zhanghua
2025-06-11 2ca169c85f61256fb5185c078dba1bfef2be5066
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
package cn.lili.common.utils;
 
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.NoHttpResponseException;
import org.apache.http.client.HttpRequestRetryHandler;
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.protocol.HttpClientContext;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
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.util.EntityUtils;
 
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.URI;
import java.net.UnknownHostException;
import java.util.Map;
 
/**
 * HttpClientUtils
 *
 * @author Bulbasaur
 * @since 2021/7/9 1:40 上午
 */
@Slf4j
public class HttpClientUtils {
 
    /**
     * org.apache.http.impl.client.CloseableHttpClient
     */
    private static CloseableHttpClient httpClient = null;
 
    //这里就直接默认固定了,因为以下三个参数在新建的method中仍然可以重新配置并被覆盖.
    /**
     * ms毫秒,从池中获取链接超时时间
     */
    static final int CONNECTION_REQUEST_TIMEOUT = 30000;
    /**
     * ms毫秒,建立链接超时时间
     */
    static final int CONNECT_TIMEOUT = 60000;
    /**
     * ms毫秒,读取超时时间
     */
    static final int SOCKET_TIMEOUT = 60000;
 
    /**
     * 总配置,主要涉及是以下两个参数,如果要作调整没有用到properties会比较后麻烦,但鉴于一经粘贴,随处可用的特点,就不再做依赖性配置化处理了.
     * 而且这个参数同一家公司基本不会变动.
     * 最大总并发,很重要的参数
     */
    static final int MAX_TOTAL = 500;
    /**
     * 每路并发,很重要的参数
     */
    static final int MAX_PER_ROUTE = 100;
 
    /**
     * 正常情况这里应该配成MAP或LIST
     * 细化配置参数,用来对每路参数做精细化处理,可以管控各ip的流量,比如默认配置请求baidu:80端口最大100个并发链接,
     * 每个细化配置之ip(不重要,在特殊场景很有用)
     */
    static final String DETAIL_HOST_NAME = "http://www.baidu.com";
 
    /**
     * 每个细化配置之port(不重要,在特殊场景很有用)
     */
    static final int DETAIL_PORT = 80;
    /**
     * 每个细化配置之最大并发数(不重要,在特殊场景很有用)
     */
    static final int DETAIL_MAX_PER_ROUTE = 100;
 
    private synchronized static CloseableHttpClient getHttpClient() {
        if (null == httpClient) {
            httpClient = init();
        }
        return httpClient;
    }
 
    /**
     * 链接池初始化 这里最重要的一点理解就是. 让CloseableHttpClient 一直活在池的世界里, 但是HttpPost却一直用完就消掉.
     * 这样可以让链接一直保持着.
     */
    private static CloseableHttpClient init() {
        CloseableHttpClient newHotpoint;
 
        //设置连接池
        ConnectionSocketFactory plainsf = PlainConnectionSocketFactory.getSocketFactory();
        LayeredConnectionSocketFactory sslsf = SSLConnectionSocketFactory.getSocketFactory();
        Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create().register("http", plainsf).register("https", sslsf).build();
        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(registry);
        //将最大连接数增加
        cm.setMaxTotal(MAX_TOTAL);
        //将每个路由基础的连接增加
        cm.setDefaultMaxPerRoute(MAX_PER_ROUTE);
 
        //细化配置开始,其实这里用Map或List的for循环来配置每个链接,在特殊场景很有用.
        //将每个路由基础的连接做特殊化配置,一般用不着
        HttpHost httpHost = new HttpHost(DETAIL_HOST_NAME, DETAIL_PORT);
        //将目标主机的最大连接数增加
        cm.setMaxPerRoute(new HttpRoute(httpHost), DETAIL_MAX_PER_ROUTE);
        //细化配置结束
 
        //请求重试处理
        HttpRequestRetryHandler httpRequestRetryHandler = (exception, executionCount, context) -> {
            if (executionCount >= 2) {//如果已经重试了2次,就放弃
                return false;
            }
            if (exception instanceof NoHttpResponseException) {//如果服务器丢掉了连接,那么就重试
                return true;
            }
            if (exception instanceof SSLHandshakeException) {//不要重试SSL握手异常
                return false;
            }
            if (exception instanceof InterruptedIOException) {//超时
                return false;
            }
            if (exception instanceof UnknownHostException) {//目标服务器不可达
                return false;
            }
            if (exception instanceof SSLException) {//SSL握手异常
                return false;
            }
 
            HttpClientContext clientContext = HttpClientContext.adapt(context);
            HttpRequest request = clientContext.getRequest();
            //如果请求是幂等的,就再次尝试
            return !(request instanceof HttpEntityEnclosingRequest);
        };
 
        //配置请求的超时设置
        RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(CONNECTION_REQUEST_TIMEOUT).setConnectTimeout(CONNECT_TIMEOUT).setSocketTimeout(SOCKET_TIMEOUT).build();
        newHotpoint = HttpClients.custom().setConnectionManager(cm).setDefaultRequestConfig(requestConfig).setRetryHandler(httpRequestRetryHandler).build();
        return newHotpoint;
    }
 
    public static String doGet(String url, Map<String, String> param) {
 
        //httpClient
        CloseableHttpClient httpClient = getHttpClient();
 
        String resultString = "";
        CloseableHttpResponse response = null;
        try {
            //创建uri
            URIBuilder builder = new URIBuilder(url);
            if (param != null) {
                for (String key : param.keySet()) {
                    builder.addParameter(key, param.get(key));
                }
            }
            URI uri = builder.build();
 
            //创建http GET请求
            HttpGet httpGet = new HttpGet(uri);
 
            //执行请求
            response = httpClient.execute(httpGet);
            //判断返回状态是否为200
            if (response.getStatusLine().getStatusCode() == 200) {
                resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
            }
        } catch (Exception e) {
            log.error("get请求错误", e);
        } finally {
            try {
                if (response != null) {
                    response.close();
                }
            } catch (IOException e) {
                log.error("Get错误", e);
            }
        }
        return resultString;
    }
}