HttpClient4使用连接池
本文实验,不是通过springboot配置的,直接引用jar来实现。
工具类供参考。
使用连接池工具类
package com.figo.test.utils;import com.figo.util.Logger;
import net.sf.json.JSONObject;
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
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.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;/*** HttpClient使用连接池* HttpClient-4.5.14.jar* HttpCore-4.4.16.jar*/
public class HttpClientUseConnPoolUtil {/*** 日志.*/private static Logger logger = Logger.getLogger(HttpClientUseConnPoolUtil.class);
// private static final Logger log = LoggerFactory.getLogger(HttpClientUseConnPoolUtil.class);private static volatile CloseableHttpClient httpClient = null;static PoolingHttpClientConnectionManager connectionManager;private HttpClientUseConnPoolUtil() {}/*** 获取连接池状态,leased已释放,pending请求中,available可用,max最大* [leased: 0; pending: 0; available: 3; max: 200]* @return*/public static String getStatus(){if(connectionManager!=null) {return connectionManager.getTotalStats().toString();}else{return "";}}/*** 单例,获取httpclient* @return*/private static CloseableHttpClient getHttpClient() {if (Objects.isNull(httpClient)) {synchronized (HttpClientUseConnPoolUtil.class) {if (Objects.isNull(httpClient)) {connectionManager = new PoolingHttpClientConnectionManager();// 连接池总容量connectionManager.setMaxTotal(200);// 每个host为一组,此参数用于控制每组中连接池的容量 【重要】connectionManager.setDefaultMaxPerRoute(20);RequestConfig requestConfig = RequestConfig.custom()// 从连接池中取连接超时时间.setConnectionRequestTimeout(30000)// 建立链接超时时间.setConnectTimeout(30000)// 等待读取数据时间.setSocketTimeout(30000).build();httpClient = HttpClientBuilder.create()// 关闭自动重试.disableAutomaticRetries()// 设置连接池.setConnectionManager(connectionManager)// setConnectionTimeToLive(2, TimeUnit.MINUTES) 设置链接最大存活时间 此属性无效 注意// 回收空闲超过2分钟的链接.evictIdleConnections(2, TimeUnit.MINUTES)// 设置超时时间.setDefaultRequestConfig(requestConfig).build();}}}return httpClient;}/*** 不带header参数的JSON格式Post请求** @param url 请求URL* @param bodyJson body参数* @return 响应体*/public static String httpPost(String url, JSONObject bodyJson) {return httpPost(url, null, bodyJson);}/*** 携带header参数的JSON格式Post请求** @param url 请求URL* @param headerParam header参数* @param bodyJson body参数* @return 响应体*/public static String httpPost(String url, Map<String, String> headerParam, JSONObject bodyJson) {HttpPost httpPost = new HttpPost(url);httpPost.addHeader("Content-Type", "application/json");// 组装headerif (headerParam!=null) {for (String key : headerParam.keySet()) {httpPost.addHeader(key, headerParam.get(key));}}// 组装bodyif (Objects.nonNull(bodyJson)) {StringEntity stringEntity = new StringEntity(bodyJson.toString(), StandardCharsets.UTF_8);stringEntity.setContentType("application/json");stringEntity.setContentEncoding(StandardCharsets.UTF_8.name());httpPost.setEntity(stringEntity);}// 不要关闭client,会导致连接池被关闭CloseableHttpClient client = getHttpClient();try (CloseableHttpResponse response = client.execute(httpPost)) {HttpEntity entity = response.getEntity();String result = EntityUtils.toString(entity, StandardCharsets.UTF_8.name());EntityUtils.consume(entity);return result;} catch (Exception e) {logger.error(e.getMessage(), e);throw new RuntimeException(e);}}/*** Get请求** @param url 请求URL* @return 响应体*/public static String httpGet(String url) {return httpGet(url, null, null);}/*** 携带url参数的Get请求** @param url 请求URL* @param urlParam url参数* @return 响应体*/public static String httpGet(String url, Map<String, String> urlParam) {return httpGet(url, null, urlParam);}/*** 携带header参数与url参数的Get请求** @param url 请求URL* @param headerParam header参数* @param urlParam url参数* @return 响应体*/public static String httpGet(String url, Map<String, String> headerParam, Map<String, String> urlParam) {HttpGet httpGet = new HttpGet();// 组装headerif (headerParam!=null) {for (String key : headerParam.keySet()) {httpGet.addHeader(key, headerParam.get(key));}}try {// 组装url参数URIBuilder uriBuilder = new URIBuilder(url);if (urlParam!=null) {for (String key : urlParam.keySet()) {uriBuilder.addParameter(key, urlParam.get(key));}}httpGet.setURI(uriBuilder.build());} catch (Exception e) {logger.error(e.getMessage(), e);throw new RuntimeException(e);}// 不要关闭client,会导致连接池被关闭CloseableHttpClient client = getHttpClient();try (CloseableHttpResponse response = client.execute(httpGet)) {HttpEntity entity = response.getEntity();String result = EntityUtils.toString(entity, StandardCharsets.UTF_8.name());EntityUtils.consume(entity);return result;} catch (Exception e) {logger.error(e.getMessage(), e);throw new RuntimeException(e);}}
}
不使用连接池工具类
/*** */
package com.figo.test.utils;import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;import javax.net.ssl.SSLContext;import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;import com.figo.util.Logger;/*** @author figo**/
public class HttpClientUtils {/*** 日志.*/private static Logger logger = Logger.getLogger(HttpClientUtils.class);/*** 带参数的post请求 .* * @param url .* @param map .* @return .* @throws Exception .*/public static HttpResult doPostObject(String url, Map<String, Object> map) throws Exception {logger.info("doPostObject请求url="+url+",params="+map);// 声明httpPost请求HttpPost httpPost = new HttpPost(url);// 加入配置信息RequestConfig config = RequestConfig.custom().setConnectionRequestTimeout(60000).setConnectTimeout(60000).setSocketTimeout(10000).build();httpPost.setConfig(config);// 判断map是否为空,不为空则进行遍历,封装from表单对象if (map != null) {List<NameValuePair> list = new ArrayList<NameValuePair>();for (Map.Entry<String, Object> entry : map.entrySet()) {list.add(new BasicNameValuePair(entry.getKey(), String.valueOf(entry.getValue())));}// 构造from表单对象UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(list, "UTF-8");// 把表单放到post里httpPost.setEntity(urlEncodedFormEntity);}SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {// 信任所有public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {return true;}}).build();SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext);HttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();// 发起请求HttpResponse response = httpClient.execute(httpPost);String respBody = EntityUtils.toString(response.getEntity(), "UTF-8");HttpResult httpResult=new HttpResult(response.getStatusLine().getStatusCode(), respBody);try {httpClient.getConnectionManager().shutdown();} catch (Exception e) {logger.error("doPostObject", e);}return httpResult;}/*** 带参数的post请求 .* * @param url .* @param map .* @return .* @throws Exception .*/public static HttpResult doPost(String url, Map<String, String> map) throws Exception {logger.info("doPost请求url="+url+",params="+map);// 声明httpPost请求HttpPost httpPost = new HttpPost(url);// 加入配置信息RequestConfig config = RequestConfig.custom().setConnectionRequestTimeout(60000).setConnectTimeout(60000).setSocketTimeout(10000).build();httpPost.setConfig(config);// 判断map是否为空,不为空则进行遍历,封装from表单对象if (map != null) {List<NameValuePair> list = new ArrayList<NameValuePair>();for (Map.Entry<String, String> entry : map.entrySet()) {list.add(new BasicNameValuePair(entry.getKey(), String.valueOf(entry.getValue())));}// 构造from表单对象UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(list, "UTF-8");// 把表单放到post里httpPost.setEntity(urlEncodedFormEntity);}SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {// 信任所有public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {return true;}}).build();SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext);HttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();// 发起请求HttpResponse response = httpClient.execute(httpPost);String respBody = EntityUtils.toString(response.getEntity(), "UTF-8");HttpResult httpResult=new HttpResult(response.getStatusLine().getStatusCode(), respBody);try {httpClient.getConnectionManager().shutdown();} catch (Exception e) {logger.error("doPostObject", e);}return httpResult;}/*** 带参数的post请求 .* * @param url .* @param map .* @return .* @throws Exception .*/public static HttpResult doPostJSON(String url, String jsonParas) throws Exception {logger.info("doPost请求url="+url+",params="+jsonParas);// 声明httpPost请求HttpPost httpPost = new HttpPost(url);// 加入配置信息RequestConfig config = RequestConfig.custom().setConnectionRequestTimeout(60000).setConnectTimeout(60000).setSocketTimeout(10000).build();httpPost.setConfig(config);// 判断map是否为空,不为空则进行遍历,封装from表单对象if (jsonParas != null) {// 把表单放到post里httpPost.setEntity(new StringEntity(jsonParas));}SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {// 信任所有public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {return true;}}).build();SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext);HttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();// 发起请求HttpResponse response = httpClient.execute(httpPost);String respBody = EntityUtils.toString(response.getEntity(), "UTF-8");HttpResult httpResult=new HttpResult(response.getStatusLine().getStatusCode(), respBody);try {httpClient.getConnectionManager().shutdown();} catch (Exception e) {logger.error("doPostObject", e);}return httpResult;}}
测试demo
package com.figo.test;import java.time.LocalDateTime;import com.figo.test.utils.HttpClientUseConnPoolUtil;
import com.figo.test.utils.HttpClientUtils;
import com.figo.test.utils.HttpResult;
import com.figo.util.Logger;import net.sf.json.JSONObject;public class HttpClientConnectionPoolTest {private static Logger logger = Logger.getLogger(HttpClientConnectionPoolTest.class);public static void main(String[] args) {try {System.out.println("使用连接池开始时间="+LocalDateTime.now());logger.info("使用连接池开始时间="+LocalDateTime.now());for(int i=0;i<200;i++){String url="https://www.baidu.com";JSONObject bodyJson=new JSONObject();bodyJson.put("test", "test");String resp=HttpClientUseConnPoolUtil.httpPost(url, bodyJson);String url1="https://www.huawei.com/";String resp1=HttpClientUseConnPoolUtil.httpPost(url1, bodyJson);//System.out.print(resp);String url2="https://weixin.qq.com/";String resp2=HttpClientUseConnPoolUtil.httpPost(url2, bodyJson);logger.info("status="+HttpClientUseConnPoolUtil.getStatus());}System.out.println(HttpClientUseConnPoolUtil.getStatus());logger.info("status="+HttpClientUseConnPoolUtil.getStatus());System.out.println("使用连接池结束时间="+LocalDateTime.now());logger.info("使用连接池结束时间="+LocalDateTime.now());System.out.println("不使用连接池开始时间="+LocalDateTime.now());logger.info("不使用连接池开始时间="+LocalDateTime.now());for(int i=0;i<200;i++){String url="https://www.baidu.com";JSONObject bodyJson=new JSONObject();bodyJson.put("test", "test");HttpResult resp=HttpClientUtils.doPostJSON(url, bodyJson.toString());//System.out.print(resp);String url1="https://www.huawei.com/";HttpResult resp1=HttpClientUtils.doPostJSON(url1, bodyJson.toString());String url2="https://weixin.qq.com/";HttpResult resp2=HttpClientUtils.doPostJSON(url2, bodyJson.toString());}System.out.println("不使用连接池结束时间="+LocalDateTime.now());logger.info("不使用连接池结束时间="+LocalDateTime.now());} catch (Exception e) {// TODO: handle exception}}
}
结果比对:
访问一个网站,各200次请求
使用连接池开始时间=2024-06-12T15:49:40.987
status=[leased: 0; pending: 0; available: 2; max: 200]
使用连接池结束时间=2024-06-12T15:49:49.366
耗时 9秒
不使用连接池开始时间=2024-06-12T15:49:49.366
不使用连接池结束时间=2024-06-12T15:50:03.829
耗时 14秒
访问两个网站,各200次请求
使用连接池开始时间=2024-06-12T16:48:55.125
status=[leased: 0; pending: 0; available: 2; max: 200]
使用连接池结束时间=2024-06-12T16:49:02.582
耗时7秒
不使用连接池开始时间=2024-06-12T16:49:02.583
不使用连接池结束时间=2024-06-12T16:49:25.323
耗时23秒
访问三个网站,各200次请求
使用连接池开始时间=2024-06-12T17:48:17.139
使用连接池结束时间=2024-06-12T17:48:32.314
耗时 15秒
不使用连接池开始时间=2024-06-12T17:48:32.314
不使用连接池结束时间=2024-06-12T17:49:10.594
耗时38秒
结论:使用连接池真的香!