前段时间琢磨了一下Apache HttpClient框架细节,看了一些源码,方便查阅,这里记录一下。
框架版本
Httpclient 4.x 是生产应用的主流,因此暂时只讨论Httpclient 4.x,具体框架版本如下:
1 <dependency>
2 <groupId>org.apache.httpcomponents</groupId>
3 <artifactId>httpclient</artifactId>
4 <version>4.5.14</version>
5 </dependency>
httpclient会依赖httpcore,httpcore的版本如下:
1<dependency>
2 <groupId>org.apache.httpcomponents</groupId>
3 <artifactId>httpcomponents-core</artifactId>
4 <version>4.4.15</version>
5 </dependency>
官网说明如下:https://hc.apache.org/httpcomponents-client-4.5.x/index.html
典型用例
在生产环境,我们使用httpclient框架时一定会使用到连接池。HttpUtils工具类封装如下:
1package com.example.demo.utils;
2
3
4import java.util.Map;
5
6public class HttpUtils {
7 private static PoolingHttpClientConnectionManager cm;
8 private static String EMPTY_STR = "";
9 private static String UTF_8 = "UTF-8";
10
11 private static void init() {
12 if (cm == null) {
13 cm = new PoolingHttpClientConnectionManager();
14 // 整个连接池最大连接数
15 cm.setMaxTotal(100);
16 // 每路由最大连接数,默认值是2
17 cm.setDefaultMaxPerRoute(200);
18 }
19 }
20
21 /**
22 * 通过连接池获取HttpClient
23 *
24 * @return
25 */
26
27 private static CloseableHttpClient getHttpClient() {
28 init();
29 RequestConfig requestConfig = RequestConfig.custom()
30 // 设置连接超时时间
31 .setConnectTimeout(3000)
32 // 设置响应超时时间
33 .setSocketTimeout(3000)
34 // 设置从连接池获取链接的超时时间
35 .setConnectionRequestTimeout(3000)
36 .build();
37 return HttpClients.custom().setDefaultRequestConfig(requestConfig).setConnectionManager(cm).build();
38
39 }
40
41
42 /**
43 * @param url
44 * @return
45 */
46
47 public static String get(String url) {
48 HttpGet httpGet = new HttpGet(url);
49 return getResult(httpGet);
50 }
51
52
53 public static String get(String url, Map<String, Object> params) throws URISyntaxException {
54 URIBuilder ub = new URIBuilder(url);
55 ArrayList<NameValuePair> pairs = covertParams2NVPS(params);
56 ub.setParameters(pairs);
57 HttpGet httpGet = new HttpGet(ub.build());
58 return getResult(httpGet);
59 }
60
61
62 public static String post(String url) {
63 HttpPost httpPost = new HttpPost(url);
64 return getResult(httpPost);
65 }
66
67
68 public static String post(String url, String json) {
69
70 HttpPost httpPost = new HttpPost(url);
71
72 StringEntity entity = new StringEntity(json, "utf-8");//解决中文乱码问题
73
74 entity.setContentEncoding("UTF-8");
75
76 entity.setContentType("application/json");
77
78 httpPost.setEntity(entity);
79
80 return getResult(httpPost);
81
82 }
83 public static String post(String url, Map<String, Object> params) throws UnsupportedEncodingException {
84 HttpPost httpPost = new HttpPost(url);
85 ArrayList<NameValuePair> pairs = covertParams2NVPS(params);
86 httpPost.setEntity(new UrlEncodedFormEntity(pairs, UTF_8));
87 return getResult(httpPost);
88
89 }
90
91 private static ArrayList<NameValuePair> covertParams2NVPS(Map<String, Object> params) {
92 ArrayList<NameValuePair> pairs = new ArrayList<NameValuePair>();
93 for (Map.Entry<String, Object> param : params.entrySet()) {
94 pairs.add(new BasicNameValuePair(param.getKey(), String.valueOf(param.getValue())));
95 }
96 return pairs;
97 }
98
99
100 /**
101 * 处理Http请求
102 */
103
104 private static String getResult(HttpRequestBase request) {
105
106 CloseableHttpClient httpClient = getHttpClient();
107
108 try {
109
110 CloseableHttpResponse response = httpClient.execute(request);
111 HttpEntity entity = response.getEntity();
112 if (entity != null) {
113
114 // long len = entity.getContentLength();// -1 表示长度未知
115
116 String result = EntityUtils.toString(entity);
117
118 response.close();
119
120 // httpClient.close();
121
122 return result;
123 }
124 } catch (IOException e) {
125 e.printStackTrace();
126 }
127 return EMPTY_STR;
128 }
129}
在以上工具类中,首先getHttpClient()方法和init方法用于初始化httpclient,使用的连接池管理器是PoolingHttpClientConnectionManager,然后getResult方法是执行具体的GET和POST请求。
关于httpclient的一些认识
-
生产环境必须使用连接池,httpclient必须单例,否则会产生性能问题
-
response.close();表示的是释放连接,这行代码是必不可少的。释放连接包含两层含义:
- 第一,当连接满足可复用时,将连接归还给连接池,以便后续请求使用。
- 第二,当连接不可复用时,直接关闭连接,包括底层的Socket连接。
-
httpclient.close();表示的是关闭连接池,建议放在JVM的shutdown钩子中使用
-
连接池中的空闲连接,大多数情况下有效时间是有限的,默认是60秒,在每次请求中,httpclient框架底层会去主动检测连接是否有效,但是最佳实践是我们自己开启定时任务去关闭超时的连接。
-
连接是否复用,取决于很多条件,大体上可以理解成:对于http 1.1版本,请求和响应任何一方的header
中有 Connection:close ,则不复用连接;对于http 1.0版本,请求和响应都包含 Connection: keep-alive,连接才会复用。因此默认情况霞http 1.1是连接复用的。
HttpClient 请求头默认携带:Connection: keep-alive
Tomcat服务器响应头默认携带:
-
Connection: keep-alive
-
Keep-Alive: timeout=60
-
TCP的长连接和Http中长连接有什么关系
简单说,不是一回事。TCP长连接是指建立Socket后保活一段时间,默认是2个小时,时间很长。Http的长连接,指的是连接复用。对于Http 1.0来说,每次进行Http请求都会新建一个Socket连接,因此连接不能复用,同时,请求完毕底层的Socket连接也会关闭,因此是TCP短连接。Http1.1 则是相反的情况,不过一般Http连接有60秒的超时,超时后会关闭底层的TCP连接。
-
httpclient连接池和tomcat线程池有什么关系
httpclient框架中本身没有使用任何线程池,也就是说在从连接池中获取、使用、归还、关闭连接的各个环节,都是在当前的业务线程中执行的。当前业务线程无法从连接池中获取到连接时,就处于睡眠状态,直到被其他线程执行完毕归还连接时给唤醒。
-
补充说明
接下来我将围绕httpclient是怎么创建的和httpclient是怎么执行的这两个问题层层递进,进行阐述这个框架的关键细节。