优化HttpClient

209 阅读3分钟

说明: 因为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();
    }
}