带有连接池的Http客户端工具类HttpClientUtil

一、背景

业务开发中,经常会遇到通过http/https向下游服务发送请求。每次都要重复造轮子写HttpClient的逻辑,而且性能、功能参差不齐。这里分享一个高性能的、带连接池的通用Http客户端工具。

请尊重作者劳动成果,转载请标明原文链接:https://www.cnblogs.com/waterystone/p/11551280.html

二、特点

  • 基于apache的高性能Http客户端org.apache.http.client.HttpClient;
  • 连接池的最大连接数默认是20,可通过系统变量-Dadu.common.http.max.total=200指定;
  • 连接池的每个路由的最大连接数默认是2,可通过系统变量-Dadu.common.http.max.per.route=10指定;
  • 可设置超时,通过HttpOptions进行设置;
  • 可重试,通过HttpOptions进行设置。

三、源码

参考:https://github.com/waterystone/adu-test/blob/master/src/main/java/com/adu/utils/HttpClientUtil.java

 

  1 package com.adu.utils;
  2 
  3 import com.adu.Constants;
  4 import com.adu.handler.HttpRequestRetryHandler;
  5 import com.adu.model.HttpOptions;
  6 import com.adu.model.HttpRequest;
  7 import com.adu.model.HttpResponse;
  8 import org.apache.http.Header;
  9 import org.apache.http.HttpEntity;
 10 import org.apache.http.NameValuePair;
 11 import org.apache.http.client.config.RequestConfig;
 12 import org.apache.http.client.entity.UrlEncodedFormEntity;
 13 import org.apache.http.client.methods.CloseableHttpResponse;
 14 import org.apache.http.client.methods.HttpGet;
 15 import org.apache.http.client.methods.HttpPost;
 16 import org.apache.http.client.methods.HttpRequestBase;
 17 import org.apache.http.client.utils.URIBuilder;
 18 import org.apache.http.client.utils.URLEncodedUtils;
 19 import org.apache.http.config.Registry;
 20 import org.apache.http.config.RegistryBuilder;
 21 import org.apache.http.conn.socket.ConnectionSocketFactory;
 22 import org.apache.http.conn.socket.PlainConnectionSocketFactory;
 23 import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
 24 import org.apache.http.entity.StringEntity;
 25 import org.apache.http.entity.mime.MultipartEntityBuilder;
 26 import org.apache.http.entity.mime.content.ContentBody;
 27 import org.apache.http.impl.client.CloseableHttpClient;
 28 import org.apache.http.impl.client.HttpClients;
 29 import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
 30 import org.apache.http.message.BasicNameValuePair;
 31 import org.apache.http.util.EntityUtils;
 32 import org.slf4j.Logger;
 33 import org.slf4j.LoggerFactory;
 34 
 35 import javax.net.ssl.SSLContext;
 36 import java.nio.charset.StandardCharsets;
 37 import java.util.ArrayList;
 38 import java.util.HashMap;
 39 import java.util.List;
 40 import java.util.Map;
 41 import java.util.Objects;
 42 import java.util.stream.Collectors;
 43 
 44 /**
 45  * 带有连接池的Http客户端工具类。具有如下特点:
 46  * <ol>
 47  * <li>基于apache的高性能Http客户端{@link org.apache.http.client.HttpClient};</li>
 48  * <li>连接池的最大连接数默认是20,可通过{@link #init(int, int)}、或者系统变量-Dzzarch.common.http.max.total=200指定;</li>
 49  * <li>连接池的每个路由的最大连接数默认是2,可通过{@link #init(int, int)}、或者系统变量-Dzzarch.common.http.max.per.route=10指定;</li>
 50  * <li>可设置超时,通过{@link HttpOptions}进行设置;</li>
 51  * <li>可重试,通过{@link HttpOptions}进行设置;</li>
 52  * </ol>
 53  *
 54  * @author duyunjie
 55  * @date 2019-09-18 16:33
 56  */
 57 public class HttpClientUtil {
 58     private static final Logger logger = LoggerFactory.getLogger(HttpClientUtil.class);
 59 
 60     /**
 61      * HttpClient 连接池
 62      */
 63     private static PoolingHttpClientConnectionManager CONNECTION_MANAGER = buildPoolingHttpClientConnectionManager(null, null);
 64 
 65     /**
 66      * @param maxTotal    连接池的最大连接数,默认为20。
 67      * @param maxPerRoute 连接池的每个路由的最大连接数,默认为2。
 68      */
 69     public static void init(int maxTotal, int maxPerRoute) {
 70         CONNECTION_MANAGER = buildPoolingHttpClientConnectionManager(maxTotal, maxPerRoute);
 71     }
 72 
 73 
 74     public static HttpResponse httpGet(HttpRequest httpRequest) throws Exception {
 75         return httpGet(httpRequest, null);
 76     }
 77 
 78     /**
 79      * 发送 HTTP GET请求
 80      *
 81      * @param httpRequest 请求参数,如url,header等。
 82      * @param httpOptions 配置参数,如重试次数、超时时间等。
 83      * @return
 84      * @throws Exception
 85      */
 86     public static HttpResponse httpGet(HttpRequest httpRequest, HttpOptions httpOptions) throws Exception {
 87         // 装载请求地址和参数
 88         URIBuilder ub = new URIBuilder(httpRequest.getUrl());
 89 
 90         // 转换请求参数
 91         List<NameValuePair> pairs = convertParams2NVPS(httpRequest.getParams());
 92         if (!pairs.isEmpty()) {
 93             ub.setParameters(pairs);
 94         }
 95         HttpGet httpGet = new HttpGet(ub.build());
 96 
 97         // 设置请求头
 98         if (Objects.nonNull(httpRequest.getHeaders())) {
 99             for (Map.Entry<String, String> header : httpRequest.getHeaders().entrySet()) {
100                 httpGet.addHeader(header.getKey(), String.valueOf(header.getValue()));
101             }
102         }
103 
104         return doHttp(httpGet, httpOptions);
105     }
106 
107 
108     public static HttpResponse httpPost(HttpRequest httpRequest) throws Exception {
109         return httpPost(httpRequest, null);
110     }
111 
112 
113     /**
114      * 发送 HTTP POST请求
115      *
116      * @param httpRequest 请求参数
117      * @param httpOptions 配置参数
118      * @return
119      * @throws Exception
120      */
121     public static HttpResponse httpPost(HttpRequest httpRequest, HttpOptions httpOptions) throws Exception {
122         HttpPost httpPost = new HttpPost(httpRequest.getUrl());
123 
124         // 转换请求参数
125         List<NameValuePair> pairs = convertParams2NVPS(httpRequest.getParams());
126         if (!pairs.isEmpty()) {
127             httpPost.setEntity(new UrlEncodedFormEntity(pairs, StandardCharsets.UTF_8.name()));
128         }
129 
130         // 设置请求头
131         if (Objects.nonNull(httpRequest.getHeaders())) {
132             for (Map.Entry<String, String> header : httpRequest.getHeaders().entrySet()) {
133                 httpPost.addHeader(header.getKey(), String.valueOf(header.getValue()));
134             }
135         }
136 
137         return doHttp(httpPost, httpOptions);
138     }
139 
140 
141     /**
142      * 发送 HTTP POST请求,参数格式JSON
143      * <p>请求参数是JSON格式,数据编码是UTF-8</p>
144      *
145      * @param url
146      * @param param
147      * @return
148      * @throws Exception
149      */
150     public static HttpResponse httpPostJson(String url, String param, HttpOptions httpOptions) throws Exception {
151         HttpPost httpPost = new HttpPost(url);
152 
153         // 设置请求头
154         httpPost.addHeader("Content-Type", "application/json; charset=UTF-8");
155 
156         // 设置请求参数
157         httpPost.setEntity(new StringEntity(param, StandardCharsets.UTF_8.name()));
158 
159         return doHttp(httpPost, httpOptions);
160     }
161 
162     /**
163      * 发送 HTTP POST请求,参数格式XML
164      * <p>请求参数是XML格式,数据编码是UTF-8</p>
165      *
166      * @param url
167      * @param param
168      * @return
169      * @throws Exception
170      */
171     public static HttpResponse httpPostXml(String url, String param, HttpOptions httpOptions) throws Exception {
172         HttpPost httpPost = new HttpPost(url);
173 
174         // 设置请求头
175         httpPost.addHeader("Content-Type", "application/xml; charset=UTF-8");
176 
177         // 设置请求参数
178         httpPost.setEntity(new StringEntity(param, StandardCharsets.UTF_8.name()));
179 
180         return doHttp(httpPost, httpOptions);
181     }
182 
183     /**
184      * 通过post发送multipart信息。
185      *
186      * @param url
187      * @param multiparts
188      * @param httpOptions
189      * @return
190      * @throws Exception
191      */
192     public static HttpResponse httpPostMultipart(String url, Map<String, ContentBody> multiparts, HttpOptions httpOptions) throws Exception {
193         HttpPost httpPost = new HttpPost(url);
194 
195         // 设置Multipart
196         if (Objects.nonNull(multiparts)) {
197             MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create();
198             for (Map.Entry<String, ContentBody> multipartEntry : multiparts.entrySet()) {
199                 multipartEntityBuilder.addPart(multipartEntry.getKey(), multipartEntry.getValue());
200             }
201 
202             httpPost.setEntity(multipartEntityBuilder.build());
203         }
204 
205         return doHttp(httpPost, httpOptions);
206     }
207 
208 
209     /**
210      * 转换请求参数,将Map键值对拼接成QueryString字符串
211      *
212      * @param params
213      * @return
214      */
215     public static String convertParams2QueryStr(Map<String, ?> params) {
216         List<NameValuePair> pairs = convertParams2NVPS(params);
217 
218         return URLEncodedUtils.format(pairs, StandardCharsets.UTF_8.name());
219     }
220 
221     /**
222      * 转换请求参数
223      *
224      * @param params
225      * @return
226      */
227     public static List<NameValuePair> convertParams2NVPS(Map<String, ?> params) {
228         if (Objects.isNull(params)) {
229             return new ArrayList<>();
230         }
231 
232         return params.entrySet().stream().map(param -> new BasicNameValuePair(param.getKey(), String.valueOf(param.getValue()))).collect(Collectors.toList());
233     }
234 
235     /**
236      * 发送 HTTP 请求
237      *
238      * @param request
239      * @return
240      * @throws Exception
241      */
242     private static HttpResponse doHttp(HttpRequestBase request, HttpOptions httpOptions) throws Exception {
243         if (Objects.isNull(httpOptions)) {//如果为空,则用默认的。
244             httpOptions = HttpOptions.DEFAULT_HTTP_OPTION;
245         }
246         // 设置超时时间
247         if (Objects.nonNull(httpOptions.getTimeoutMs())) {
248             request.setConfig(RequestConfig.custom().setSocketTimeout(httpOptions.getTimeoutMs()).build());
249         }
250 
251         //设置重试策略
252         HttpRequestRetryHandler httpRequestRetryHandler = null;
253         if (Objects.nonNull(httpOptions.getRetryCount())) {
254             httpRequestRetryHandler = new HttpRequestRetryHandler(httpOptions.getRetryCount());
255         }
256 
257         // 通过连接池获取连接对象
258         CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(CONNECTION_MANAGER).setRetryHandler(httpRequestRetryHandler).build();
259         return doRequest(httpClient, request);
260 
261     }
262 
263     /**
264      * 处理Http/Https请求,并返回请求结果
265      * <p>注:默认请求编码方式 UTF-8</p>
266      *
267      * @param httpClient
268      * @param request
269      * @return
270      * @throws Exception
271      */
272     private static HttpResponse doRequest(CloseableHttpClient httpClient, HttpRequestBase request) throws Exception {
273         HttpResponse res = new HttpResponse();
274         CloseableHttpResponse response = null;
275         long start = System.currentTimeMillis();
276 
277         try {
278             // 获取请求结果
279             response = httpClient.execute(request);
280 
281             // 解析请求结果
282             HttpEntity entity = response.getEntity();
283             String result = EntityUtils.toString(entity, StandardCharsets.UTF_8.name()); // 转换结果
284             EntityUtils.consume(entity); // 关闭IO流
285 
286             //解析返回header
287             Map<String, String> headers = new HashMap<>(response.getAllHeaders().length);
288             for (Header header : response.getAllHeaders()) {
289                 headers.put(header.getName(), header.getValue());
290             }
291 
292             res.setStatusCode(response.getStatusLine().getStatusCode()).setResult(result).setHeaders(headers);
293         } finally {
294             if (Objects.nonNull(response)) {
295                 response.close();
296             }
297         }
298 
299         long elapsed = System.currentTimeMillis() - start;
300         logger.debug("op=end_doRequest,request={},res={},elapsed={}", request, res, elapsed);
301         return res;
302     }
303 
304 
305     /**
306      * 初始化连接池
307      *
308      * @return
309      */
310     private static PoolingHttpClientConnectionManager buildPoolingHttpClientConnectionManager(Integer maxTotal, Integer maxPerRoute) {
311         // 初始化连接池,可用于请求HTTP/HTTPS(信任所有证书)
312         PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(getRegistry());
313 
314         // 整个连接池的最大连接数
315         String maxTotalProperty = null;
316         if (Objects.nonNull(maxTotal)) { //首先看有没有在参数中显式指定
317             connectionManager.setMaxTotal(maxTotal);
318         } else { //如果没有在参数中显式指定,则再看有没有在系统变量中指定
319             maxTotalProperty = System.getProperty(Constants.SYSTEM_PROPERTY_KEY_HTTP_MAX_TOTAL);
320             if (Objects.nonNull(maxTotalProperty)) {
321                 connectionManager.setMaxTotal(Integer.valueOf(maxTotalProperty));
322             }
323         }
324 
325         // 每个路由的最大连接数
326         String maxPerRouteProperty = null;
327         if (Objects.nonNull(maxPerRoute)) { //首先看有没有在参数中显式指定
328             connectionManager.setDefaultMaxPerRoute(maxPerRoute);
329         } else { //如果没有在参数中显式指定,则再看有没有在系统变量中指定
330             maxPerRouteProperty = System.getProperty(Constants.SYSTEM_PROPERTY_KEY_HTTP_MAX_PER_ROUTE);
331             if (Objects.nonNull(maxPerRouteProperty)) {
332                 connectionManager.setDefaultMaxPerRoute(Integer.valueOf(maxPerRouteProperty));
333             }
334         }
335 
336         logger.info("[ZZARCH_COMMON_SUCCESS_buildPoolingHttpClientConnectionManager]maxTotal={},maxPerRoute={},maxTotalProperty={},maxPerRouteProperty={}", maxTotal, maxPerRoute, maxTotalProperty, maxPerRouteProperty);
337         return connectionManager;
338     }
339 
340 
341     /**
342      * 获取 HTTPClient注册器
343      *
344      * @return
345      * @throws Exception
346      */
347     private static Registry<ConnectionSocketFactory> getRegistry() {
348         try {
349             return RegistryBuilder.<ConnectionSocketFactory>create().register("http", new PlainConnectionSocketFactory()).register("https", new SSLConnectionSocketFactory(SSLContext.getDefault())).build();
350         } catch (Exception e) {
351             logger.error("[ERROR_getRegistry]", e);
352         }
353 
354         return null;
355     }
356 }
View Code

 

 

end

 

posted @ 2019-09-19 17:33 waterystone 阅读(...) 评论(...) 编辑 收藏