AmazingCounters.com

HttpClient工具类

  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 }
  1. 上述http工具使用了http连接池管理器,设置了连接池中最多创建100个http连接,最少有60个连接。
  2. 通过使用单例,保证只创建一次httpClient对象,项目中使用http请求时,无需重复创,直接从http连接池中获取。
  3. 在创建httpClient对象时,自定义了重试策略、长连接超时时长、以及对http连接池中的连接进行检查、清理的策略。
posted @ 2024-10-09 15:36  小明今晚不加班  阅读(6)  评论(0编辑  收藏  举报