一、背景

业务开发中,经常会遇到通过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

 package com.adu.utils;

 import com.adu.Constants;
import com.adu.handler.HttpRequestRetryHandler;
import com.adu.model.HttpOptions;
import com.adu.model.HttpRequest;
import com.adu.model.HttpResponse;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.ContentBody;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import javax.net.ssl.SSLContext;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors; /**
* 带有连接池的Http客户端工具类。具有如下特点:
* <ol>
* <li>基于apache的高性能Http客户端{@link org.apache.http.client.HttpClient};</li>
* <li>连接池的最大连接数默认是20,可通过{@link #init(int, int)}、或者系统变量-Dzzarch.common.http.max.total=200指定;</li>
* <li>连接池的每个路由的最大连接数默认是2,可通过{@link #init(int, int)}、或者系统变量-Dzzarch.common.http.max.per.route=10指定;</li>
* <li>可设置超时,通过{@link HttpOptions}进行设置;</li>
* <li>可重试,通过{@link HttpOptions}进行设置;</li>
* </ol>
*
* @author duyunjie
* @date 2019-09-18 16:33
*/
public class HttpClientUtil {
private static final Logger logger = LoggerFactory.getLogger(HttpClientUtil.class); /**
* HttpClient 连接池
*/
private static PoolingHttpClientConnectionManager CONNECTION_MANAGER = buildPoolingHttpClientConnectionManager(null, null); /**
* @param maxTotal 连接池的最大连接数,默认为20。
* @param maxPerRoute 连接池的每个路由的最大连接数,默认为2。
*/
public static void init(int maxTotal, int maxPerRoute) {
CONNECTION_MANAGER = buildPoolingHttpClientConnectionManager(maxTotal, maxPerRoute);
} public static HttpResponse httpGet(HttpRequest httpRequest) throws Exception {
return httpGet(httpRequest, null);
} /**
* 发送 HTTP GET请求
*
* @param httpRequest 请求参数,如url,header等。
* @param httpOptions 配置参数,如重试次数、超时时间等。
* @return
* @throws Exception
*/
public static HttpResponse httpGet(HttpRequest httpRequest, HttpOptions httpOptions) throws Exception {
// 装载请求地址和参数
URIBuilder ub = new URIBuilder(httpRequest.getUrl()); // 转换请求参数
List<NameValuePair> pairs = convertParams2NVPS(httpRequest.getParams());
if (!pairs.isEmpty()) {
ub.setParameters(pairs);
}
HttpGet httpGet = new HttpGet(ub.build()); // 设置请求头
if (Objects.nonNull(httpRequest.getHeaders())) {
for (Map.Entry<String, String> header : httpRequest.getHeaders().entrySet()) {
httpGet.addHeader(header.getKey(), String.valueOf(header.getValue()));
}
} return doHttp(httpGet, httpOptions);
} public static HttpResponse httpPost(HttpRequest httpRequest) throws Exception {
return httpPost(httpRequest, null);
} /**
* 发送 HTTP POST请求
*
* @param httpRequest 请求参数
* @param httpOptions 配置参数
* @return
* @throws Exception
*/
public static HttpResponse httpPost(HttpRequest httpRequest, HttpOptions httpOptions) throws Exception {
HttpPost httpPost = new HttpPost(httpRequest.getUrl()); // 转换请求参数
List<NameValuePair> pairs = convertParams2NVPS(httpRequest.getParams());
if (!pairs.isEmpty()) {
httpPost.setEntity(new UrlEncodedFormEntity(pairs, StandardCharsets.UTF_8.name()));
} // 设置请求头
if (Objects.nonNull(httpRequest.getHeaders())) {
for (Map.Entry<String, String> header : httpRequest.getHeaders().entrySet()) {
httpPost.addHeader(header.getKey(), String.valueOf(header.getValue()));
}
} return doHttp(httpPost, httpOptions);
} /**
* 发送 HTTP POST请求,参数格式JSON
* <p>请求参数是JSON格式,数据编码是UTF-8</p>
*
* @param url
* @param param
* @return
* @throws Exception
*/
public static HttpResponse httpPostJson(String url, String param, HttpOptions httpOptions) throws Exception {
HttpPost httpPost = new HttpPost(url); // 设置请求头
httpPost.addHeader("Content-Type", "application/json; charset=UTF-8"); // 设置请求参数
httpPost.setEntity(new StringEntity(param, StandardCharsets.UTF_8.name())); return doHttp(httpPost, httpOptions);
} /**
* 发送 HTTP POST请求,参数格式XML
* <p>请求参数是XML格式,数据编码是UTF-8</p>
*
* @param url
* @param param
* @return
* @throws Exception
*/
public static HttpResponse httpPostXml(String url, String param, HttpOptions httpOptions) throws Exception {
HttpPost httpPost = new HttpPost(url); // 设置请求头
httpPost.addHeader("Content-Type", "application/xml; charset=UTF-8"); // 设置请求参数
httpPost.setEntity(new StringEntity(param, StandardCharsets.UTF_8.name())); return doHttp(httpPost, httpOptions);
} /**
* 通过post发送multipart信息。
*
* @param url
* @param multiparts
* @param httpOptions
* @return
* @throws Exception
*/
public static HttpResponse httpPostMultipart(String url, Map<String, ContentBody> multiparts, HttpOptions httpOptions) throws Exception {
HttpPost httpPost = new HttpPost(url); // 设置Multipart
if (Objects.nonNull(multiparts)) {
MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create();
for (Map.Entry<String, ContentBody> multipartEntry : multiparts.entrySet()) {
multipartEntityBuilder.addPart(multipartEntry.getKey(), multipartEntry.getValue());
} httpPost.setEntity(multipartEntityBuilder.build());
} return doHttp(httpPost, httpOptions);
} /**
* 转换请求参数,将Map键值对拼接成QueryString字符串
*
* @param params
* @return
*/
public static String convertParams2QueryStr(Map<String, ?> params) {
List<NameValuePair> pairs = convertParams2NVPS(params); return URLEncodedUtils.format(pairs, StandardCharsets.UTF_8.name());
} /**
* 转换请求参数
*
* @param params
* @return
*/
public static List<NameValuePair> convertParams2NVPS(Map<String, ?> params) {
if (Objects.isNull(params)) {
return new ArrayList<>();
} return params.entrySet().stream().map(param -> new BasicNameValuePair(param.getKey(), String.valueOf(param.getValue()))).collect(Collectors.toList());
} /**
* 发送 HTTP 请求
*
* @param request
* @return
* @throws Exception
*/
private static HttpResponse doHttp(HttpRequestBase request, HttpOptions httpOptions) throws Exception {
if (Objects.isNull(httpOptions)) {//如果为空,则用默认的。
httpOptions = HttpOptions.DEFAULT_HTTP_OPTION;
}
// 设置超时时间
if (Objects.nonNull(httpOptions.getTimeoutMs())) {
request.setConfig(RequestConfig.custom().setSocketTimeout(httpOptions.getTimeoutMs()).build());
} //设置重试策略
HttpRequestRetryHandler httpRequestRetryHandler = null;
if (Objects.nonNull(httpOptions.getRetryCount())) {
httpRequestRetryHandler = new HttpRequestRetryHandler(httpOptions.getRetryCount());
} // 通过连接池获取连接对象
CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(CONNECTION_MANAGER).setRetryHandler(httpRequestRetryHandler).build();
return doRequest(httpClient, request); } /**
* 处理Http/Https请求,并返回请求结果
* <p>注:默认请求编码方式 UTF-8</p>
*
* @param httpClient
* @param request
* @return
* @throws Exception
*/
private static HttpResponse doRequest(CloseableHttpClient httpClient, HttpRequestBase request) throws Exception {
HttpResponse res = new HttpResponse();
CloseableHttpResponse response = null;
long start = System.currentTimeMillis(); try {
// 获取请求结果
response = httpClient.execute(request); // 解析请求结果
HttpEntity entity = response.getEntity();
String result = EntityUtils.toString(entity, StandardCharsets.UTF_8.name()); // 转换结果
EntityUtils.consume(entity); // 关闭IO流 //解析返回header
Map<String, String> headers = new HashMap<>(response.getAllHeaders().length);
for (Header header : response.getAllHeaders()) {
headers.put(header.getName(), header.getValue());
} res.setStatusCode(response.getStatusLine().getStatusCode()).setResult(result).setHeaders(headers);
} finally {
if (Objects.nonNull(response)) {
response.close();
}
} long elapsed = System.currentTimeMillis() - start;
logger.debug("op=end_doRequest,request={},res={},elapsed={}", request, res, elapsed);
return res;
} /**
* 初始化连接池
*
* @return
*/
private static PoolingHttpClientConnectionManager buildPoolingHttpClientConnectionManager(Integer maxTotal, Integer maxPerRoute) {
// 初始化连接池,可用于请求HTTP/HTTPS(信任所有证书)
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(getRegistry()); // 整个连接池的最大连接数
String maxTotalProperty = null;
if (Objects.nonNull(maxTotal)) { //首先看有没有在参数中显式指定
connectionManager.setMaxTotal(maxTotal);
} else { //如果没有在参数中显式指定,则再看有没有在系统变量中指定
maxTotalProperty = System.getProperty(Constants.SYSTEM_PROPERTY_KEY_HTTP_MAX_TOTAL);
if (Objects.nonNull(maxTotalProperty)) {
connectionManager.setMaxTotal(Integer.valueOf(maxTotalProperty));
}
} // 每个路由的最大连接数
String maxPerRouteProperty = null;
if (Objects.nonNull(maxPerRoute)) { //首先看有没有在参数中显式指定
connectionManager.setDefaultMaxPerRoute(maxPerRoute);
} else { //如果没有在参数中显式指定,则再看有没有在系统变量中指定
maxPerRouteProperty = System.getProperty(Constants.SYSTEM_PROPERTY_KEY_HTTP_MAX_PER_ROUTE);
if (Objects.nonNull(maxPerRouteProperty)) {
connectionManager.setDefaultMaxPerRoute(Integer.valueOf(maxPerRouteProperty));
}
} logger.info("[ZZARCH_COMMON_SUCCESS_buildPoolingHttpClientConnectionManager]maxTotal={},maxPerRoute={},maxTotalProperty={},maxPerRouteProperty={}", maxTotal, maxPerRoute, maxTotalProperty, maxPerRouteProperty);
return connectionManager;
} /**
* 获取 HTTPClient注册器
*
* @return
* @throws Exception
*/
private static Registry<ConnectionSocketFactory> getRegistry() {
try {
return RegistryBuilder.<ConnectionSocketFactory>create().register("http", new PlainConnectionSocketFactory()).register("https", new SSLConnectionSocketFactory(SSLContext.getDefault())).build();
} catch (Exception e) {
logger.error("[ERROR_getRegistry]", e);
} return null;
}
}

end

带有连接池的Http客户端工具类HttpClientUtil的更多相关文章

  1. 关于jedis2.4以上版本的连接池配置,及工具类

    jedis.propertise 注意以前版本的maxAcitve和maxWait有所改变,JVM根据系统环境变量ServerType中的值 取不同的配置,实现多环境(测试环境.生产环境)集成. re ...

  2. Redis进阶实践之九 独立封装的RedisClient客户端工具类

    一.引言 今天开始有关Redis学习的第九篇文章了,以后肯定会大量系统使用Redis作为缓存介质,为了更好的更好的Redis,自己写了两个工具类,但是这两个工具类,没有提供一致的接口,是为了使用的独立 ...

  3. Redis进阶实践之九 独立封装的RedisClient客户端工具类(转载9)

    Redis进阶实践之九 独立封装的RedisClient客户端工具类 一.引言 今天开始有关Redis学习的第九篇文章了,以后肯定会大量系统使用Redis作为缓存介质,为了更好的更好的Redis,自己 ...

  4. HTTP请求客户端工具类

    1.maven 引入依赖 <dependency> <groupId>commons-httpclient</groupId> <artifactId> ...

  5. 带连接池的netty客户端核心功能实现剖解

    带连接池的netty客户端核心功能实现剖析 带连接池的netty的客户端核心功能实现剖析 本文为原创,转载请注明出处 源码地址: https://github.com/zhangxianwu/ligh ...

  6. Java jdbc 连接oracle之三(封装工具类)

    driver = oracle.jdbc.driver.OracleDriver url = jdbc:oracle:thin:@192.168.10.105:1521:orcl user = LF ...

  7. Jesery客户端工具类

    public class JerseyClientUtil { public static<T> T sendMsg(String url,Object object,Class<T ...

  8. 【JAVAWEB学习笔记】10_JDBC连接池&DBUtils

    使用连接池改造JDBC的工具类: 1.1.1          需求: 传统JDBC的操作,对连接的对象销毁不是特别好.每次创建和销毁连接都是需要花费时间.可以使用连接池优化的程序. * 在程序开始的 ...

  9. C3P0连接池、DBCP连接池

    C3P0连接池: 配置文件:c3p0-config.xml <?xml version="1.0" encoding="UTF-8"?> <c ...

随机推荐

  1. ICT638 Mobile and App Development

    Assessment Cover SheetStudent ID CohortStudent NameProgrammeEnrolledDiploma in Information Technolog ...

  2. Mybatis中的Mapper.xml映射文件sql查询接收多个参数

    ​ 我们都知道,在Mybatis中的Mapper.xml映射文件可以定制动态SQL,在dao层定义的接口中定义的参数传到xml文件中之后,在查询之前mybatis会对其进行动态解析,通常使用#{}接收 ...

  3. 【LOJ#3144】[APIO2019]奇怪装置(数论)

    [LOJ#3144][APIO2019]奇怪装置(数论) 题面 LOJ 题解 突然发现\(LOJ\)上有\(APIO\)的题啦,赶快来做一做. 这题是窝考场上切了的题嗷.写完暴力之后再推了推就推出正解 ...

  4. 【转】测试开发工程师必备软硬能力&高级测试开发工程师需要具备什么能力?

    对于测试的基本知识,可以查看软件测试相关书籍 对于在公司成为一位优秀的测试开发工程师,我觉得下面这篇文章涉及到的是我们需要的,稍微进行改动https://blog.csdn.net/sinat_210 ...

  5. Linux磁盘系统——管理磁盘的命令

    Linux磁盘系统——管理磁盘的命令 摘要:本文主要学习了Linux系统中管理磁盘的命令,包括查看磁盘使用情况.磁盘挂载相关.磁盘分区相关.磁盘格式化等操作. df命令 df命令用于显示Linux系统 ...

  6. 深入浅出《设计模式》之外观模式(C++)

    前言 模式介绍 外观模式相比较之下比较简单,模式设计中定义是为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口是的这一子系统更加容易使用. 如果不理解呢,简单些说就是外观模式提 ...

  7. 英语AquilariaCrassna奇楠沉香

    越南奇楠沉香Aquilaria crassna是瑞香科沉香属植物. 奇楠香被喻为沉香中的钻石,其与身俱来的香气,淡雅宜人,汇集天地阴阳五行之气,而成为唯一能通三界之香品.长久以来,它被视为一种珍贵罕有 ...

  8. linux 广播和组播

    广播和组播 广播,必须使用UDP协议,是只能在局域网内使用,指定接收端的IP为*.*.*.255后,发送的信息,局域网内的所有接受端就能够接到信息了. 广播的发送端代码 #include <st ...

  9. 华为云fusionsphere 6.1组件功能

      [fsp@controller-21 ~]$ openstack --version ##fusionsphere 6.1基于openstack 2.2.1 [fsp@controller-21 ...

  10. docker研究-5 docker网络介绍

    例子:启动(创建)一个容器,自定义容器名字为my_nginxtest02,镜像为nginx,将宿主机(本机)81端口映射到容器的80端口 [root@localhost ~]# docker run ...