HttpClient 工具类
1package cn.linuxcrypt.utils;
2
3import org.apache.http.*;
4import org.apache.http.client.ClientProtocolException;
5import org.apache.http.client.ResponseHandler;
6import org.apache.http.client.config.CookieSpecs;
7import org.apache.http.client.config.RequestConfig;
8import org.apache.http.client.entity.UrlEncodedFormEntity;
9import org.apache.http.client.methods.CloseableHttpResponse;
10import org.apache.http.client.methods.HttpGet;
11import org.apache.http.client.methods.HttpPost;
12import org.apache.http.client.methods.HttpRequestBase;
13import org.apache.http.client.protocol.HttpClientContext;
14import org.apache.http.config.ConnectionConfig;
15import org.apache.http.config.Registry;
16import org.apache.http.config.RegistryBuilder;
17import org.apache.http.conn.ConnectionKeepAliveStrategy;
18import org.apache.http.conn.socket.ConnectionSocketFactory;
19import org.apache.http.conn.socket.PlainConnectionSocketFactory;
20import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
21import org.apache.http.entity.ContentType;
22import org.apache.http.impl.client.CloseableHttpClient;
23import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
24import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
25import org.apache.http.message.BasicHeader;
26import org.apache.http.message.BasicHeaderElementIterator;
27import org.apache.http.message.BasicNameValuePair;
28import org.apache.http.protocol.HTTP;
29import org.apache.http.protocol.HttpContext;
30import org.apache.http.ssl.SSLContextBuilder;
31import org.apache.http.util.Args;
32import org.apache.http.util.EntityUtils;
33
34import javax.net.ssl.SSLContext;
35import javax.net.ssl.TrustManager;
36import javax.net.ssl.X509TrustManager;
37import java.io.IOException;
38import java.nio.charset.Charset;
39import java.nio.charset.CodingErrorAction;
40import java.security.cert.X509Certificate;
41import java.util.*;
42
43public final class HttpClients {
44 // 设置整个连接池最大连接数
45 private static int POOL_MAX_TOTAL = 2;
46 /**
47 * 设置整个连接池最大连接数
48 *
49 * @param maxTotal
50 */
51 public static void setPoolMaxTotal(int maxTotal) {
52 synchronized (HttpClients.class) {
53 POOL_MAX_TOTAL = maxTotal;
54 HttpClientPool.setMaxTotal(maxTotal);
55 }
56 }
57
58 /**
59 * http 连接池
60 */
61 static class HttpClientPool {
62 private static PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = null;
63 // 设置每个路由上的默认连接个数,setMaxPerRoute则单独为某个站点设置最大连接个数。
64 private static final int POOL_MAX_PER_ROUTER = 1;
65 private static final String DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:60.0) Gecko/20100101 Firefox/60.0";
66 // keepalive
67 private static int DEFAULT_KEEP_ALIVE = 30 * 1000;
68 /**
69 * 从连接池中获取请求连接的超时时间 单位毫秒
70 * -1: 系统默认的超时时间,内核级配置
71 * 0: 无限制。
72 * 具体参考{@link org.apache.http.client.config.RequestConfig#getConnectionRequestTimeout()}
73 */
74 public static final int DEFAULT_CONNECTION_REQUEST_TIMEOUT = -1;
75
76 // 默认连接超时时间
77 public static final int DEFAULT_CONNECT_TIMEOUT = 10000;
78
79 // 默认socket读取数据超时时间,具体的长耗时请求中(如文件传送等)必须覆盖此设置
80 public static final int DEFAULT_SO_TIMEOUT = 15000;
81
82 static {
83 Registry<ConnectionSocketFactory> socketFactoryRegistry = null;
84 try {
85 final SSLContext sslContext = SSLContextBuilder.create().build();
86 sslContext.init(null, new TrustManager[]{
87 new X509TrustManager() {
88 public X509Certificate[] getAcceptedIssuers() {
89 return null;
90 }
91
92 public void checkClientTrusted(X509Certificate[] certs, String authType) {
93 }
94
95 public void checkServerTrusted(X509Certificate[] certs, String authType) {
96 }
97 }
98 }, null);
99 socketFactoryRegistry = RegistryBuilder
100 .<ConnectionSocketFactory>create()
101 .register("http", PlainConnectionSocketFactory.INSTANCE)
102 .register("https", new SSLConnectionSocketFactory(sslContext)).build();
103 } catch (Exception e) {
104
105 }
106
107 poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
108
109 //连接池的最大连接数
110 poolingHttpClientConnectionManager.setMaxTotal(POOL_MAX_TOTAL);
111
112 /**
113 * 设置每个路由上的默认连接个数,setMaxPerRoute则单独为某个站点设置最大连接个数。
114 *
115 * DefaultMaxPerRoute是根据连接到的主机对MaxTotal的一个细分;比如:
116 * MaxtTotal=400 DefaultMaxPerRoute=200
117 * 而我只连接到http://a.com时,到这个主机的并发最多只有200;而不是400;
118 * 而我连接到http://a.com 和 http://b.com时,到每个主机的并发最多只有200;即加起来是400(但不能超过400;所以起作用的设置是DefaultMaxPerRoute。
119 */
120 poolingHttpClientConnectionManager.setDefaultMaxPerRoute(POOL_MAX_PER_ROUTER);
121
122 // 默认连接配置
123 ConnectionConfig connectionConfig = ConnectionConfig.custom()
124 .setMalformedInputAction(CodingErrorAction.IGNORE)
125 .setUnmappableInputAction(CodingErrorAction.IGNORE)
126 .setCharset(Consts.UTF_8)
127 .build();
128 poolingHttpClientConnectionManager.setDefaultConnectionConfig(connectionConfig);
129 }
130
131 public static void setMaxTotal(int maxTotal) {
132 poolingHttpClientConnectionManager.setMaxTotal(maxTotal);
133 }
134
135 /**
136 * 增加默认的http 头
137 *
138 * @return
139 * @{link https://www.cnblogs.com/lwhkdash/archive/2012/10/14/2723252.html}
140 */
141 private static Set<Header> defaultHeaders() {
142 Set<Header> header = new HashSet<>();
143
144 Header accept = new BasicHeader(HttpHeaders.ACCEPT,
145 "text/html,application/xhtml+xml,application/json,application/xml;q=0.9,*/*;q=0.8");
146 header.add(accept);
147 Header acceptEncoding = new BasicHeader(HttpHeaders.ACCEPT_ENCODING, "gzip, deflate");
148 header.add(acceptEncoding);
149 Header acceptLanguage = new BasicHeader(HttpHeaders.ACCEPT_LANGUAGE, "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3");
150 header.add(acceptLanguage);
151 Header connect = new BasicHeader(HttpHeaders.CONNECTION, "keep-alive");
152 header.add(connect);
153 Header acceptCharset = new BasicHeader(HttpHeaders.ACCEPT_CHARSET, Consts.UTF_8.name());
154 header.add(acceptCharset);
155
156 // DO NOT TRACK的缩写,要求服务器程序不要跟踪记录用户信息。DNT: 1 (开启DNT) DNT: 0 (关闭DNT)火狐,safari,IE9都支持这个头域,并且于2011年3月7日被提交至IETF组织实现标准化
157 Header dnt = new BasicHeader("DNT", "1");
158 header.add(dnt);
159
160 return header;
161 }
162
163 /**
164 * 获取 HttpClient
165 * @return
166 */
167 public static CloseableHttpClient getHttpClient() {
168 return getHttpClient(DEFAULT_SO_TIMEOUT, DEFAULT_CONNECT_TIMEOUT, 0);
169 }
170
171 /**
172 * 默认keepAlive策略:如果响应中存在服务器端的keepAlive超时时间则返回该时间否则返回默认的
173 */
174 public static class DefaultConnectionKeepAliveStrategy implements ConnectionKeepAliveStrategy {
175 public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
176 HeaderElementIterator it = new BasicHeaderElementIterator(response.headerIterator(HTTP.CONN_KEEP_ALIVE));
177 while (it.hasNext()) {
178 HeaderElement he = it.nextElement();
179 String param = he.getName();
180 String value = he.getValue();
181 if (value != null && param.equalsIgnoreCase("timeout")) {
182 try {
183 return Long.parseLong(value) * 1000;
184 } catch (NumberFormatException ignore) {
185 }
186 }
187 }
188 return DEFAULT_KEEP_ALIVE; //默认30秒
189 }
190 }
191
192 public static CloseableHttpClient getHttpClient(int socketTimeout, int connectTimeout, int retryCount) {
193 RequestConfig globalConfig = RequestConfig.custom()
194 .setCookieSpec(CookieSpecs.IGNORE_COOKIES)
195 .setSocketTimeout(socketTimeout)
196 .setConnectionRequestTimeout(DEFAULT_CONNECTION_REQUEST_TIMEOUT)
197 .setConnectTimeout(connectTimeout)
198 .build();
199
200 CloseableHttpClient closeableHttpClient = org.apache.http.impl.client.HttpClients
201 .custom()
202 .setConnectionManager(poolingHttpClientConnectionManager)
203 .setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy())
204 // 另外设置http client的重试次数,默认是3次;当前是禁用掉(如果项目量不到,这个默认即可)
205 .setRetryHandler(new DefaultHttpRequestRetryHandler(retryCount, false))
206 .setUserAgent(DEFAULT_USER_AGENT)
207 .setDefaultHeaders(defaultHeaders())
208 .setDefaultRequestConfig(globalConfig)
209 .setConnectionManagerShared(true)
210 .evictExpiredConnections()// 开启超时清理线程
211 .build();
212
213 return closeableHttpClient;
214 }
215 }
216
217 /**
218 * 对于特殊请求(比如请求涉及到cookie的处理,鉴权认证等),默认的一些配置已经满足不了了,
219 * 这时就可以使用一个独立于全局的配置来执行请求,这个独立于全局,又不会干扰其他线程的请求执行的机制就是使用HttpClientContext,
220 * 该设置类用于对已经提供的一个基于全局配置的副本,来设置一些配置(见HttpClientContext.setXxx)
221 */
222 public static interface HttpClientContextSetter {
223 public void setHttpClientContext(HttpClientContext context);
224 }
225
226 /**
227 * <p>执行http请求</p>
228 *
229 * @param httpMethod - HTTP请求(HttpGet、HttpPost等等)
230 * @param httpClientContextSetter - 可选参数,请求前的一些参数设置(如:cookie、鉴权认证等)
231 * @param responseHandler - 必选参数,响应处理类(如针对httpstatu的各种值做一些策略处理等等)
232 * @return 推荐使用 org.apache.http.impl.client.CloseableHttpClient#execute( org.apache.http.HttpHost,
233 * org.apache.http.HttpRequest,
234 * org.apache.http.client.ResponseHandler,
235 * org.apache.http.protocol.HttpContext)
236 */
237 public static <T> T doHttpRequest(HttpRequestBase httpMethod, HttpClientContextSetter httpClientContextSetter, ResponseHandler<T> responseHandler) {
238 Args.notNull(httpMethod, "Parameter 'httpMethod' can not be null!");
239 Args.notNull(responseHandler, "Parameter 'responseHandler' can not be null!");
240 CloseableHttpResponse response = null;
241 try {
242 if (httpClientContextSetter != null) {
243 HttpClientContext context = HttpClientContext.create();
244 httpClientContextSetter.setHttpClientContext(context);
245 response = HttpClientPool.getHttpClient().execute(httpMethod, context);
246 } else {
247 response = HttpClientPool.getHttpClient().execute(httpMethod);
248 }
249 return response == null ? null : responseHandler.handleResponse(response);
250 } catch (Exception e) {
251 throw new RuntimeException(e.getMessage(), e);
252 } finally {
253 if (response != null) {
254 try {
255 response.close();
256 } catch (IOException e) {
257 }
258 }
259 }
260 }
261
262 /**
263 * 默认的处理返回值为String的ResponseHandler
264 */
265 public static class DefaultStringResponseHandler implements ResponseHandler<String> {
266 /**
267 * 默认响应html字符集编码
268 */
269 private Charset defaultCharset = Consts.UTF_8;
270
271 public DefaultStringResponseHandler() {
272 super();
273 }
274
275 public DefaultStringResponseHandler(String defaultCharset) {
276 super();
277 this.defaultCharset = Charset.forName(defaultCharset);
278 }
279
280 public Charset getDefaultCharset() {
281 return defaultCharset;
282 }
283
284 public void setDefaultCharset(Charset defaultCharset) {
285 this.defaultCharset = defaultCharset;
286 }
287
288 public String handleResponse(HttpResponse response) throws ClientProtocolException, IOException {
289 HttpEntity httpEntity = response.getEntity();
290 if (httpEntity != null) {
291 return EntityUtils.toString(httpEntity, defaultCharset == null ? ContentType.getOrDefault(httpEntity).getCharset() : defaultCharset);
292 }
293 return null;
294 }
295 }
296
297 /**
298 * <p>根据URL和参数创建HttpPost对象</p>
299 *
300 * @param url
301 * @param paramMap
302 * @return
303 */
304 public static HttpPost createHttpPost(String url, Map<String, String> paramMap) {
305 try {
306 HttpPost httpPost = new HttpPost(url);
307 if (paramMap != null && !paramMap.isEmpty()) {
308 List<NameValuePair> params = new ArrayList<NameValuePair>();
309 for (Map.Entry<String, String> entry : paramMap.entrySet()) {
310 params.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
311 }
312 UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(params, Consts.UTF_8.name());
313 httpPost.setEntity(formEntity);
314 }
315 return httpPost;
316 } catch (Exception e) {
317 throw new RuntimeException(e.getMessage(), e);
318 }
319 }
320
321 public static String get(String url) {
322 HttpGet httpGet = new HttpGet(url);
323 String value = doHttpRequest(httpGet, null, new DefaultStringResponseHandler());
324 return value;
325 }
326
327 public static String post(String url, Map<String, String> param){
328 HttpPost post = createHttpPost(url, param);
329 return doHttpRequest(post, null, new DefaultStringResponseHandler());
330 }
331}
总结
DefaultMaxPerRoute
和MaxTotal
配置 DefaultMaxPerRoute是根据连接到的主机对MaxTotal的一个细分;比如: MaxtTotal=400 DefaultMaxPerRoute=200 而我只连接到http://a.com时,到这个主机的并发最多只有200;而不是400; 而我连接到http://a.com 和 http://b.com时,到每个主机的并发最多只有200;即加起来是400(但不能超过400;所以起作用的设置是DefaultMaxPerRoute。- 超时设置
- connectionRequestTimeout: 从连接池中获取请求连接的超时时间 单位毫秒, -1:系统默认的超时时间,内核级配置; 0:无限制
- connectTimeout: 默认连接超时时间
- soTimeout: 默认socket读取数据超时时间,具体的长耗时请求中(如文件传送等)必须覆盖此设置
- 策略
- pool.evictExpiredConnections(true): 后台启动一个线程,进行超时连接处理
- 当获取可用连接时,采用LRU进行处理连接。
- 池中池
- httpclient在初始化时,设置的MaxTotal的参数为总的连接池
- 在最大的池中,根据主机的名字(route)进行小池的划分
- 在动态获取可用连接的时候采用
LRU
算法,清理或者释放连接
- available集合和leased集合
- 见 org.apache.http.pool.AbstractConnPool
- leased集合当前租用的连接
- available集合当前可用的连接
- LRU会根据MaxTotal、leased集合总数、available集合总数进行LRU淘汰。
- Future 进行一步接收数据
- 持久连接
- HTTP/1.1采取持久连接的方式替代了Keep-Alive
- HTTP/1.1的连接默认情况下都是持久连接。如果要显式关闭,需要在报文中加上Connection:Close首部。即在HTTP/1.1中,所有的连接都进行了复用
- 两种方式,空闲的持久连接也可以随时被客户端与服务端关闭。不发送Connection:Close不意味着服务器承诺连接永远保持打开。
- EntityUtils.toString(HttpEntity, …)和EntityUtils.consume(HttpEntity);
- 注意此方法会关闭InputStream,因为是多路复用,每次读取完必须关闭,否则不能被复用
- CloseableHttpResponse在每次请求完进行reponse.close()
- HttpClient不能关闭,否则连接都会重新建立,会发起tcp的3次握手连接和4次握手断开
测试
抓包测试池化后网络连接3次握手建立连接和4次握手断开连接(抓包只看到3次)
评论