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;
|
}
|
}
|