• 带有连接池的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

  • 相关阅读:
    蛙蛙推荐:一个程序员2012年技术学习总结
    蛙蛙推荐:第一堂编程课提纲
    蛙蛙推荐:笨办法提高代码质量
    蛙蛙推荐:Backbone和seajs搭配最佳实践探讨
    时髦的互联网公司都在用什么技术?
    蛙蛙推荐:让SecureCRT好使起来
    Linux LVM卷组管理 规格严格
    聊聊jdbc statement的fetchSize 规格严格
    老生常谈: Eclipse远程调试 规格严格
    产品经理的34个感想
  • 原文地址:https://www.cnblogs.com/waterystone/p/11551280.html
Copyright © 2020-2023  润新知