1 package xx;
2 import java.io.IOException;
3 import java.io.InterruptedIOException;
4 import java.io.Serializable;
5 import java.net.ConnectException;
6 import java.net.SocketTimeoutException;
7 import java.net.URLEncoder;
8 import java.net.UnknownHostException;
9 import java.security.KeyManagementException;
10 import java.security.NoSuchAlgorithmException;
11 import java.security.cert.CertificateException;
12 import java.security.cert.X509Certificate;
13 import java.util.ArrayList;
14 import java.util.Arrays;
15 import java.util.Collection;
16 import java.util.Map;
17 import java.util.concurrent.TimeUnit;
18 import javax.net.ssl.SSLContext;
19 import javax.net.ssl.SSLException;
20 import javax.net.ssl.TrustManager;
21 import javax.net.ssl.X509TrustManager;
22 import javax.servlet.http.HttpServletRequest;
23 import org.apache.commons.lang3.StringUtils;
24 import org.apache.http.HeaderElement;
25 import org.apache.http.HeaderElementIterator;
26 import org.apache.http.HttpEntity;
27 import org.apache.http.HttpEntityEnclosingRequest;
28 import org.apache.http.HttpRequest;
29 import org.apache.http.HttpStatus;
30 import org.apache.http.NameValuePair;
31 import org.apache.http.NoHttpResponseException;
32 import org.apache.http.client.ClientProtocolException;
33 import org.apache.http.client.HttpRequestRetryHandler;
34 import org.apache.http.client.config.RequestConfig;
35 import org.apache.http.client.entity.UrlEncodedFormEntity;
36 import org.apache.http.client.methods.CloseableHttpResponse;
37 import org.apache.http.client.methods.HttpGet;
38 import org.apache.http.client.methods.HttpPost;
39 import org.apache.http.client.methods.HttpRequestBase;
40 import org.apache.http.client.protocol.HttpClientContext;
41 import org.apache.http.client.utils.URIBuilder;
42 import org.apache.http.config.Registry;
43 import org.apache.http.config.RegistryBuilder;
44 import org.apache.http.conn.ConnectionKeepAliveStrategy;
45 import org.apache.http.conn.socket.ConnectionSocketFactory;
46 import org.apache.http.conn.socket.PlainConnectionSocketFactory;
47 import org.apache.http.conn.ssl.NoopHostnameVerifier;
48 import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
49 import org.apache.http.entity.ContentType;
50 import org.apache.http.entity.StringEntity;
51 import org.apache.http.impl.client.CloseableHttpClient;
52 import org.apache.http.impl.client.HttpClients;
53 import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
54 import org.apache.http.message.BasicHeaderElementIterator;
55 import org.apache.http.message.BasicNameValuePair;
56 import org.apache.http.protocol.HTTP;
57 import org.apache.http.util.Args;
58 import org.apache.http.util.EntityUtils;
59 import org.json.JSONObject;
60
61 public class HttpClientUtil implements Serializable {
62 private static final long serialVersionUID = -4614180760247345326 L;
63 private static Logger logger = LoggerFactory.getLogger(HttpClientUtil.class);
64 // 指客户端和服务器建立连接的timeout
65 private static final int DEFAULT_HTTP_CONNECT_TIMEOUT = 3000;
66 // 指客户端和服务器建立连接后,客户端从服务器读取数据的timeout
67 private static final int DEFAULT_HTTP_SO_TIMEOUT = 15000;
68 // 从连接池获取连接的timeout
69 private static final int DEFAULT_CONNECT_REQ_TIMEOUT = 3000;
70 // 重用空闲连接时会先检查是否空闲时间超过这个时间,如果超过,释放socket重新建立
71 private static int VALIDATE_AFTER_INACTIVITY = 2000;
72 // 设置client:两个request请求之间的时间间隔,超过这个时间连接就断开
73 private static long KEEP_ALIVE_TIMEOUT = 10000L;
74 // 设置驱逐空闲连接的时间
75 private static int EVICT_IDLE_CONNECTION_TIMEOUT = 10000;
76 /** * THREAD_POOL_MAX_TOTAL:线程池的最大连接数 */
77 private static int THREAD_POOL_MAX_TOTAL = 100;
78 /** * THREAD_POOL_MAX_PER_ROUTE:线程池中每通路由的最大并发数 */
79 private static int THREAD_POOL_MAX_PER_ROUTE = 60;
80 /** * http重试次数 */
81 private static int HTTP_RETRY_TIMES = 2;
82
83 private volatile static PoolingHttpClientConnectionManager cm;
84 private volatile static CloseableHttpClient httpClient;
85 private final static Object syncLock = new Object();
86 private static String UTF_8 = "UTF-8";
87
88 private static Registry < ConnectionSocketFactory > getSocketFactoryRegistry() throws NoSuchAlgorithmException, KeyManagementException {
89 TrustManager trustManager = new X509TrustManager() {
90 @Override
91 public X509Certificate[] getAcceptedIssuers() {
92 return null;
93 }
94 @Override
95 public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {}
96 @Override
97 public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {}
98 };
99 // 开启SSL
100 SSLContext sslContext = SSLContext.getInstance("TLS");
101 // 初始化ssl校验
102 sslContext.init(null, new TrustManager[] {trustManager}, null);
103 // 注册ssl链接工厂
104 SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
105 // 链接配置
106 return RegistryBuilder.<ConnectionSocketFactory>create()
107 .register("http", PlainConnectionSocketFactory.INSTANCE)
108 .register("https", socketFactory)
109 .build();
110 }
111
112 /**
113 * 通过连接池获取HttpClient
114 */
115 private static CloseableHttpClient getHttpClient()
116 throws NoSuchAlgorithmException, KeyManagementException {
117 if (httpClient == null) {
118 synchronized(syncLock) {
119 if (httpClient == null) {
120 /**
121 * 这里创建http连接池管理器时是用来自定义的Registry对象,使其支持SSL安全连接
122 * */
123 cm = new PoolingHttpClientConnectionManager(getSocketFactoryRegistry());
124 // 整个连接池最大连接数
125 cm.setMaxTotal(THREAD_POOL_MAX_TOTAL);
126 // 每路由最大连接数,默认值是2 (其实就是设置连接池中的最小连接数)
127 cm.setDefaultMaxPerRoute(THREAD_POOL_MAX_PER_ROUTE);
128 // 设置当连接空闲X毫秒再使用时对该http连接进行校验,避免http连接处于半开状态等。
129 cm.setValidateAfterInactivity(VALIDATE_AFTER_INACTIVITY);
130 RequestConfig requestConfig = RequestConfig.custom()
131 .setConnectionRequestTimeout(DEFAULT_CONNECT_REQ_TIMEOUT)
132 .setConnectTimeout(DEFAULT_HTTP_CONNECT_TIMEOUT)
133 .setSocketTimeout(DEFAULT_HTTP_SO_TIMEOUT).build();
134
135 /**
136 * keepAliveStrategy:定义keep-alive策略,客户端固定设置keepAlive时间为10秒
137 * (如果请求头中有设置超时时长,则按照请求头设置的超时来计算,否则按照默认设置的超时时长来)
138 */
139 ConnectionKeepAliveStrategy keepAliveStrategy = ((response, context) - > {
140 Args.notNull(response, "HTTP response");
141 final HeaderElementIterator it = new BasicHeaderElementIterator(response.headerIterator(HTTP.CONN_KEEP_ALIVE));
142 while (it.hasNext()) {
143 final HeaderElement he = it.nextElement();
144 final String param = he.getName();
145 final String value = he.getValue();
146 if (value != null && param.equalsIgnoreCase("timeout")) {
147 try {
148 return Long.parseLong(value) * 1000;
149 } catch (final NumberFormatException ignore) {
150 logger.error("解析response中header为keep-alive的值出错..", ignore);
151 }
152 }
153 }
154 return KEEP_ALIVE_TIMEOUT;
155 });
156
157 /** * HttpRequestRetryHandler:设置重试机制 */
158 HttpRequestRetryHandler retryHandler = (exception, executionCount, context) - > {
159 /** * 1.重试次数 */
160 if (executionCount > HTTP_RETRY_TIMES) {
161 // Do not retry if over max retry count
162 return false;
163 }
164
165 /** * 2.nonRetriableClasses里的类不重试 */
166 Collection < Class << ? extends IOException >> nonRetriableClasses = Arrays.asList(
167 InterruptedIOException.class,
168 UnknownHostException.class,
169 SocketTimeoutException.class,
170 SSLException.class);
171 if (nonRetriableClasses.contains(exception.getClass())) {
172 return false;
173 } else {
174 for (final Class << ? extends IOException > rejectException : nonRetriableClasses) {
175 if (rejectException.isInstance(exception)) {
176 return false;
177 }
178 }
179 }
180
181 /** * 3.幂等的可以重试(比如get请求就是幂等的,而post请求就不是幂等的) */
182 HttpClientContext clientContext = HttpClientContext.adapt(context);
183 HttpRequest request = clientContext.getRequest();
184 boolean idemponent = !(request instanceof HttpEntityEnclosingRequest);
185 if (idemponent) {
186 // Retry if the request is considered idempotent
187 return true;
188 }
189
190 /** * 4.指定下面的异常需要重试 */
191 if (exception instanceof NoHttpResponseException || exception instanceof ConnectException) {
192 return true;
193 }
194 // otherwise do not retry
195 return false;
196 };
197 httpClient = HttpClients.custom()
198 .setConnectionManager(cm)
199 .setDefaultRequestConfig(requestConfig)
200 .setRetryHandler(retryHandler)
201 .evictExpiredConnections()
202 .evictIdleConnections(EVICT_IDLE_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)
203 .setKeepAliveStrategy(keepAliveStrategy)
204 .build();
205 }
206 }
207 }
208 return httpClient;
209 }
210
211 /**Get请求,入参为Map */
212 public static String httpGet(String url, Map<String,Object > params, String businessScenario) {
213 String requestLog = "";
214 try {
215 requestLog = String.format("业务场景=%s,HttpGet请求url=%s,请求入参=%s", businessScenario, url, new JSONObject(params));
216 URIBuilder uriBuilder = new URIBuilder();
217 uriBuilder.setPath(url);
218 ArrayList < NameValuePair > pairs = covertParams2NameValuePairs(params);
219 uriBuilder.setParameters(pairs);
220 HttpGet httpGet = new HttpGet(uriBuilder.build());
221 return getResult(httpGet, requestLog);
222 } catch (Exception e) {
223 logger.error(String.format("请求异常.%s,请求返回结果=%s", requestLog, ""), e);
224 }
225 return ResultUtil.fail(ExceptionEnum.HTTP_EXECUTE_ERROR);
226 }
227
228 /**Get请求,不带入参 */
229 public static String httpGet(String url, String businessScenario) {
230 String requestLog = "";
231 try {
232 requestLog = String.format("业务场景=%s,HttpGet请求url=%s,请求入参=", businessScenario, url);
233 HttpGet httpGet = new HttpGet(url);
234 return getResult(httpGet, requestLog);
235 } catch (Exception e) {
236 logger.error(String.format("请求异常.%s,请求返回结果=%s", requestLog, ""), e);
237 }
238 return ResultUtil.fail(ExceptionEnum.HTTP_EXECUTE_ERROR);
239 }
240
241 /**Get请求,入参为String */
242 public static String httpGet(String url, String params, String businessScenario) {
243 String requestLog = "";
244 try {
245 requestLog = String.format("业务场景=%s,HttpGet请求url=%s,请求入参=%s", businessScenario, url, params);
246 String paramsStr = URLEncoder.encode(params, UTF_8).replaceAll("\\+", "%20");
247 HttpGet httpGet = new HttpGet(url + "/" + paramsStr);
248 return getResult(httpGet, requestLog);
249 } catch (Exception e) {
250 logger.error(String.format("请求异常.%s,请求返回结果=%s", requestLog, ""), e);
251 }
252 return ResultUtil.fail(ExceptionEnum.HTTP_EXECUTE_ERROR);
253 }
254
255 /**Post请求,不带入参 */
256 public static String httpPost(String url, String businessScenario) {
257 String requestLog = "";
258 try {
259 requestLog = String.format("业务场景=%s,HttpPost请求url=%s,请求入参=", businessScenario, url);
260 HttpPost httpPost = new HttpPost(url);
261 return getResult(httpPost, requestLog);
262 } catch (Exception e) {
263 logger.error(String.format("请求异常.%s,请求返回结果=%s", requestLog, ""), e);
264 }
265 return ResultUtil.fail(ExceptionEnum.HTTP_EXECUTE_ERROR);
266 }
267
268 /**Post请求,入参为Map */
269 public static String httpPost(String url, Map<String,Object> params, String businessScenario) {
270 String requestLog = "";
271 try {
272 requestLog = String.format("业务场景=%s,HttpPost请求url=%s,请求入参=%s", businessScenario, url, new JSONObject(params));
273 HttpPost httpPost = new HttpPost(url);
274 ArrayList <NameValuePair> pairs = covertParams2NameValuePairs(params);
275 httpPost.setEntity(new UrlEncodedFormEntity(pairs, UTF_8));
276 return getResult(httpPost, requestLog);
277 } catch (Exception e) {
278 logger.error(String.format("请求异常.%s,请求返回结果=%s", requestLog, ""), e);
279 }
280 return ResultUtil.fail(ExceptionEnum.HTTP_EXECUTE_ERROR);
281 }
282
283 /**Post请求,入参为String */
284 public static String httpPost(String url, String json, String businessScenario) {
285 String requestLog = "";
286 try {
287 requestLog = String.format("业务场景=%s,HttpPost请求url=%s,请求入参=%s", businessScenario, url, json);
288 HttpPost httpPost = new HttpPost(url);
289 httpPost.addHeader("Content-Type", "application/json");
290 StringEntity stringEntity = new StringEntity(json, ContentType.APPLICATION_JSON);
291 httpPost.setEntity(stringEntity);
292 return getResult(httpPost, requestLog);
293 } catch (Exception e) {
294 logger.error(String.format("请求异常.%s,请求返回结果=%s", requestLog, ""), e);
295 }
296 return ResultUtil.fail(ExceptionEnum.HTTP_EXECUTE_ERROR);
297 }
298
299 /** 处理Http请求 */
300 private static String getResult(HttpRequestBase request, String requestLog) {
301 CloseableHttpResponse response = null;
302 try {
303 // 请求开始时间毫秒值
304 Long requestStartTime = System.currentTimeMillis();
305 CloseableHttpClient httpClient = getHttpClient();
306 response = httpClient.execute(request);
307 // 请求完成时间毫秒值
308 Long requestEndTime = System.currentTimeMillis();
309 if (response == null) {
310 logger.error(String.format("HTTP请求无响应.%s,请求返回结果=%s,请求经历的时间=%s毫秒", requestLog, "", requestEndTime - requestStartTime));
311 return ResultUtil.fail(ExceptionEnum.HTTP_NO_RESPONSE);
312 }
313 if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
314 logger.error(String.format("HTTP请求响应异常StatusCode=%s.%s,请求返回结果=%s,请求经历的时间=%s毫秒", response.getStatusLine().getStatusCode(), requestLog, "", requestEndTime - requestStartTime));
315 return ResultUtil.fail("54103", "HTTP请求异常响应码" + response.getStatusLine().getStatusCode());
316 }
317 HttpEntity entity = response.getEntity();
318 if (entity != null) {
319 String result = EntityUtils.toString(entity, "utf-8");
320 logger.info(String.format("HTTP请求访问正常.%s,请求返回结果=%s,请求经历的时间=%s毫秒", requestLog, result, requestEndTime - requestStartTime));
321 return result;
322 }
323 } catch (Exception e) {
324 logger.error(String.format("HTTP请求异常.%s,请求返回结果=%s", requestLog, ""), e);
325 } finally {
326 if (response != null) {
327 try {
328 //把连接归还到连接池
329 EntityUtils.consume(response.getEntity());
330 } catch (IOException e) {
331 logger.error(String.format("HTTP请求异常.%s,请求返回结果=%s", requestLog, ""), e);
332 }
333 }
334 }
335 return ResultUtil.fail(ExceptionEnum.HTTP_EXECUTE_ERROR);
336 }
337
338 /** 将Map转成ArrayList<NameValuePair> */
339 private static ArrayList <NameValuePair> covertParams2NameValuePairs(Map<String, Object> params) {
340 ArrayList <NameValuePair> pairs = new ArrayList <NameValuePair> ();
341 for (Map.Entry<String, Object> param: params.entrySet()) {
342 pairs.add(new BasicNameValuePair(param.getKey(), String.valueOf(param.getValue())));
343 }
344 return pairs;
345 }
346
347 /**Post请求,入参为String,指定传输编码 */
348 public static String httpPost(String url, String json, String businessScenario, String charset) {
349 String requestLog = "";
350 try {
351 requestLog = String.format("业务场景=%s,HttpPost请求url=%s,请求入参=%s", businessScenario, url, json);
352 HttpPost httpPost = new HttpPost(url);
353 httpPost.addHeader("Content-Type", "application/json;"+charset);
354 StringEntity stringEntity = new StringEntity(json, ContentType.APPLICATION_JSON);
355 httpPost.setEntity(stringEntity);
356 return getResultCharset(httpPost, requestLog, charset);
357 } catch (Exception e) {
358 logger.error(String.format("请求异常.%s,请求返回结果=%s", requestLog, ""), e);
359 }
360 return ResultUtil.fail(ExceptionEnum.HTTP_EXECUTE_ERROR);
361 }
362
363 /** 处理指定字符编码的Http请求 */
364 private static String getResult(HttpRequestBase request, String requestLog, String charset) {
365 CloseableHttpResponse response = null;
366 try {
367 // 请求开始时间毫秒值
368 Long requestStartTime = System.currentTimeMillis();
369 CloseableHttpClient httpClient = getHttpClient();
370 response = httpClient.execute(request);
371 // 请求完成时间毫秒值
372 Long requestEndTime = System.currentTimeMillis();
373 if (response == null) {
374 logger.error(String.format("HTTP请求无响应.%s,请求返回结果=%s,请求经历的时间=%s毫秒", requestLog, "", requestEndTime - requestStartTime));
375 return ResultUtil.fail(ExceptionEnum.HTTP_NO_RESPONSE);
376 }
377 if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
378 logger.error(String.format("HTTP请求响应异常StatusCode=%s.%s,请求返回结果=%s,请求经历的时间=%s毫秒", response.getStatusLine().getStatusCode(), requestLog, "", requestEndTime - requestStartTime));
379 return ResultUtil.fail("54103", "HTTP请求异常响应码" + response.getStatusLine().getStatusCode());
380 }
381 HttpEntity entity = response.getEntity();
382 if (entity != null) {
383 String result = EntityUtils.toString(entity, charset);
384 logger.info(String.format("HTTP请求访问正常.%s,请求返回结果=%s,请求经历的时间=%s毫秒", requestLog, result, requestEndTime - requestStartTime));
385 return result;
386 }
387 } catch (Exception e) {
388 logger.error(String.format("HTTP请求异常.%s,请求返回结果=%s", requestLog, ""), e);
389 } finally {
390 if (response != null) {
391 try {
392 //把连接归还到连接池
393 EntityUtils.consume(response.getEntity());
394 } catch (IOException e) {
395 logger.error(String.format("HTTP请求异常.%s,请求返回结果=%s", requestLog, ""), e);
396 }
397 }
398 }
399 return ResultUtil.fail(ExceptionEnum.HTTP_EXECUTE_ERROR);
400 }
401 }