说明: 因为Http连接最费时的是创建过程,所以我们可以对HttpClient进行优化,使用单例模式来实现创建过程,以此加速Http连接的效率!
1. 引入依赖
<!-- httpclient相关依赖 -->
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.0.3</version>
</dependency>
<!--字符串校验-->
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
2. 创建HttpUtils类
package com.ssm.tool;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.core5.http.config.Registry;
import org.apache.hc.core5.http.config.RegistryBuilder;
import org.apache.hc.core5.http.io.SocketConfig;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.util.TimeValue;
import org.apache.hc.core5.util.Timeout;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
public class HttpUtils {
static CloseableHttpClient httpClient;
/**
* 使用静态初始化块来初始化httpClient,这意味着httpClient是一个静态变量,
* 它在类被加载到JVM时只会被初始化一次。这样做的好处是可以在整个应用程序中共享这个httpClient实例,减少资源消耗并提高性能。
*/
// !!!!!重点!!!!!
static {
// 注册http和https的相关内容(注册连接套接字工厂)
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory()) //对于HTTP协议,使用PlainConnectionSocketFactory.getSocketFactory()获取默认的套接字工厂。
.register("https", SSLConnectionSocketFactory.getSocketFactory()) //对于HTTPS协议,使用SSLConnectionSocketFactory.getSocketFactory()获取支持SSL/TLS的套接字工厂
.build();
// 创建连接池并设置相关属性
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry);
connectionManager.setMaxTotal(500); //设置连接池的最大总连接数为500,这意味着在任何给定时间,连接池可以持有的最大连接数是500。
connectionManager.setDefaultMaxPerRoute(500); //设置每个路由(可以理解为每个目标主机)的最大连接数为500,这允许对单个目标进行大量的并发连接。
connectionManager.setDefaultSocketConfig(
SocketConfig.custom()
.setSoTimeout(15, TimeUnit.SECONDS) //配置套接字选项,如设置套接字超时时间为15秒,启用TCP无延迟选项等。
.build()
);
connectionManager.setValidateAfterInactivity(TimeValue.ofSeconds(15)); //设置连接在不活动15秒后验证其有效性,这有助于及时释放无效连接。
// 配置RequestConfig(用于配置请求的超时时间和其他参数)
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(Timeout.ofSeconds(1)) //设置连接超时时间为1秒,即等待与服务器建立连接的最长时间。
.setConnectionRequestTimeout(Timeout.ofSeconds(1)) //设置连接请求超时时间为1秒,即等待从连接管理器获取可用连接的最长时间。
.setResponseTimeout(Timeout.ofSeconds(1)) //设置响应超时时间为1秒,即从服务器读取响应的最长时间。
.build();
// 创建HttpClients(开始自定义HttpClient的配置)
httpClient = HttpClients.custom()
.setConnectionManager(connectionManager) //设置连接管理器为前面创建的connectionManager
.setDefaultRequestConfig(requestConfig) //设置默认的请求配置为前面创建的requestConfig
.disableAutomaticRetries() //禁用自动重试机制,这意味着如果请求失败(如超时、连接问题等),HttpClient不会自动重试请求
.build();
}
/**
* 自定义封装方法(post方法)
* @param url 请求路径
* @param pairList pair二元组,key为请求参数的key value为请求参数的值(使用List是因为pair无法foreach遍历)
* @param headerMap 请求头信息,可能要我们手动添加请求头参数
* @return
*/
public static String post(String url, List<Pair<String, String>> pairList, Map<String, String> headerMap) throws Exception {
url = url + "?" + buildParam(pairList);
HttpPost httpPost = new HttpPost(url); //创建post请求对象
if(Objects.nonNull(headerMap) && !headerMap.isEmpty()) {
//当headerMap不为空时,要把map放到请求头中(可使用MapUtils代替)
headerMap.forEach((key, value) -> {
httpPost.addHeader(key, value);
});
}
CloseableHttpResponse response = null;
try {
response = httpClient.execute(httpPost); //发送请求,获取响应对象
return EntityUtils.toString(response.getEntity()); //将response结果转化为实体返回
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
if(response != null) {
EntityUtils.consume(response.getEntity()); //释放资源
}
}
}
//将请求参数拼接到请求路径上 xxx? name=ssm&age=18 拼接问号后面的内容
private static String buildParam(List<Pair<String, String>> pairList) {
StringBuilder stringBuilder = new StringBuilder();
for(Pair<String, String> pair : pairList) {
stringBuilder.append(pair.getKey()).append("=").append(pair.getValue()).append("$");
}
stringBuilder.setLength(stringBuilder.length() - 1); //把最后的$删除
return stringBuilder.toString();
}
}