一、拼接url
首先我们需要知道的是,url是要符合一定格式的,比如我们就不能在url中写“$”、“#”、中文、空格等。所以,我们这里采用application/x-www-form-urlencoded 格式对请求参数进行编码,可参考如下代码:
public static String buildUrl(String url, Map<String, String> params) throws UnsupportedEncodingException {
StringBuilder urlBuilder = new StringBuilder(url);
if (params != null) {
Set<String> keySet = params.keySet();
if (CollectionUtils.isNotEmpty(keySet)) {
urlBuilder.append("?");
for (String key : keySet) {
urlBuilder.append(key)
.append("=")
.append(URLEncoder.encode(params.get(key), StandardCharsets.UTF_8.name()))
.append("&");
}
urlBuilder.deleteCharAt(urlBuilder.length() - 1);
}
}
return urlBuilder.toString();
}
通过方法buildUrl ,我们就完成了url的拼接工作。
二、对参数进行编码是什么意思?
“采用application/x-www-form-urlencoded 格式对请求参数进行编码”,到底是什么意思呢?
首先我们可以先搞懂URLEncoder.encode(params.get(key), StandardCharsets.UTF_8.name()) 方法的第二个参数的作用是什么
String str = new String(charArrayWriter.toCharArray());
byte[] ba = str.getBytes(charset);
上面代码片段摘自URLEncoder 的encode 方法,charArrayWriter.toCharArray() 是待编码字符串的字符数组,charset 就是我们传入的“utf-8”。可以看出来,这里首先是将待编码字符串使用“utf-8”进行编码。当然这里也可以根据具体的需求,采用其他的编码格式。
紧接着,再继续对经过“utf-8”编码后的字符串,采用application/x-www-form-urlencoded 编码格式要求进行计算,相关算法源码如下:
String str = new String(charArrayWriter.toCharArray());
byte[] ba = str.getBytes(charset);
for (int j = 0; j < ba.length; j++) {
out.append('%');
char ch = Character.forDigit((ba[j] >> 4) & 0xF, 16);
if (Character.isLetter(ch)) {
ch -= caseDiff;
}
out.append(ch);
ch = Character.forDigit(ba[j] & 0xF, 16);
if (Character.isLetter(ch)) {
ch -= caseDiff;
}
out.append(ch);
}
总结一下,URLEncoder.encode(params.get(key), StandardCharsets.UTF_8.name()) 做了两件事:首先将待编码字符串使用“utf-8”进行编码;然后将编码结果再使用符合application/x-www-form-urlencoded 编码格式要求进行计算。经过以上两步,就完成了对字符串的编码工作。
三、读取输出流
发起请求后,返回的结果是一个InputStream,使用下面的代码读取它:
private static String getContent(InputStream inputStream) throws IOException {
try (BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(inputStream, StandardCharsets.UTF_8.name()))) {
String line;
StringBuilder result = new StringBuilder();
while ((line = bufferedReader.readLine()) != null) {
result.append(line);
}
return result.toString();
}
}
注意,对于BufferedReader 、InputStreamReader 、InputStream 三个流,只需要关闭最外层的即可,流会一层一层的向内关闭,我们可以看下BufferedReader 的close 方法源码:
public void close() throws IOException {
synchronized (lock) {
if (in == null)
return;
try {
in.close();
} finally {
in = null;
cb = null;
}
}
}
这里的in 变量,实际上就对应咱们代码里的InputStreamReader ,然后InputStreamReader 调用close 方法时,会继续向内关闭。
那么您可能也要问了,getContent 方法有关闭任何流的代码么,为什么我没看见?其实是这样的,因为BufferedReader 实现了Closeable 接口,那么只需要把BufferedReader 方法写到try代码块里,java就会自动帮助我们关闭的。
四、使用java原生方式发起get请求
java原生api支持发起get请求,示例代码如下:
public static String doGet(String url, Map<String, String> params, Map<String, String> headers) throws Exception {
String fullUrl = buildUrl(url, params);
log.info("full url is: " + fullUrl);
HttpURLConnection conn = (HttpURLConnection) new URL(fullUrl).openConnection();
conn.setRequestMethod("GET");
if (headers != null) {
Set<String> headerKeys = headers.keySet();
if (CollectionUtils.isNotEmpty(headerKeys)) {
for (String headerKey : headerKeys) {
conn.setRequestProperty(headerKey, headers.get(headerKey));
}
}
}
int statusCode = conn.getResponseCode();
if (statusCode < HttpStatus.SC_OK || statusCode >= HttpStatus.SC_MULTIPLE_CHOICES) {
log.error("status code is: " + statusCode);
log.error("error message is: " + getContent(conn.getErrorStream()));
throw new Exception("response status code wrong, status code is: " + statusCode);
}
return getContent(conn.getInputStream());
}
五、使用apache的HttpClient发起get请求
apache也提供了工具包,示例代码如下:
public static String doGetByHttpClient(String url, Map<String, String> params, Map<String, String> headers) throws Exception {
try (CloseableHttpClient client = HttpClients.createDefault()) {
String fullUrl = buildUrl(url, params);
log.info("full url is: " + fullUrl);
HttpGet httpGet = new HttpGet(fullUrl);
if (headers != null) {
Set<String> headerKeys = headers.keySet();
if (CollectionUtils.isNotEmpty(headerKeys)) {
for (String headerKey : headerKeys) {
httpGet.setHeader(headerKey, headers.get(headerKey));
}
}
}
CloseableHttpResponse response = client.execute(httpGet);
StatusLine statusLine = response.getStatusLine();
int statusCode = statusLine.getStatusCode();
if (statusCode < HttpStatus.SC_OK || statusCode >= HttpStatus.SC_MULTIPLE_CHOICES) {
log.error("status code is: " + statusCode);
log.error("error message is: " + statusLine.getReasonPhrase());
throw new Exception("response status code wrong, status code is: " + statusCode);
}
return getContent(response.getEntity().getContent());
}
}
就我写以上代码的经验来看,使用原生的或者httpClient,差别不大,我个人反而更喜欢原生的。各位大佬看自己喜好吧。
|