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 registry = RegistryBuilder.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 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; } }