httpClient对象,一般推荐单例模式,这是一个可配置性很强的对象,同时也说明初始化需要很多复杂流程,初始化成本很高,以下为一个单例设计,以后有需要,不断完善注释信息,方便以后直接使用:
/**
* @author yaoqiang
* @date 2020/9/23 10:07
* @description 对 {@link HttpClientWrapper} 做修改,不改动原类,重新编写
* @see HttpClientWrapper
*/
public class HttpClientFactory {
/**
*
* 单例设计 延迟初始化占位
*
* @return 单例 httpClient
*/
public static CloseableHttpClient getInstance() {
return HttpClientHolder.httpClient;
}
private static class HttpClientHolder {
/**
* 从连接池获取到连接的超时时间,如果是非连接池的话,该参数暂时没有发现有什么用处
*
* 连接池数量足够大,时间可以进一步压缩
*
*/
private final static int CONNECTION_REQUEST_TIMEOUT = 9000;
/**
* 获取链接超时时间,即使失败,也和服务器没有关系,因为没有连上,有可能是网络问题
* 也就是,建立链接的最大时间,也就是完成三次握手,建立tcp链接所用的时间。
*
* 尽可能小一点,链接不上,直接重试,减少程序阻塞时间
*/
private final static int CONNECT_TIMEOUT = 3000;
/**
* 指的是服务器响应超时 时间,和服务器有关。
* 重点时图片上传,涉及到大容量,设置大一点增加容错率。
*
* read()方法阻塞的最大时间,也是,两个packet的间隔时间。
*
* 如果链接taobao接口,
* 这个失败会被记录进失败概率,如果失败概率过大,很容易就被限流,限时,这真的很坑
*
* 大一点,为对方服务器减压,如果次异常抛得过于频繁,可能会被拉入黑名单。
* 链接上结果都不要,就开始重复请求,服务器压力大
*
*/
private final static int SOCKET_TIMEOUT = 5000;
public final static CloseableHttpClient httpClient;
static {
httpClient = HttpClients
.custom()
.setRetryHandler(requestRetryHandler())
.setDefaultRequestConfig(requestConfig())
.setSSLSocketFactory(sslConnectionSocketFactory())
.setConnectionManager(poolingHttpClientConnectionManager())
// 设置为true,在client close的时候,不会执行一系列官方的关闭动作
//.setConnectionManagerShared(true)
.build();
}
/**
* 测出超时重试机制为了防止超时不生效而设置
* 如果直接放回false,不重试
* 这里会根据情况进行判断是否重试
*/
public static HttpRequestRetryHandler requestRetryHandler() {
return (exception, executionCount, context) -> {
if (executionCount >= 3) {// 如果已经重试了3次,就放弃
return false;
}
if (exception instanceof NoHttpResponseException) {
return true;
}
if (exception instanceof SSLHandshakeException) {
return false;
}
if (exception instanceof InterruptedIOException) {
return true;
}
if (exception instanceof UnknownHostException) {
return false;
}
if (exception instanceof SSLException) {
return false;
}
HttpClientContext clientContext = HttpClientContext.adapt(context);
HttpRequest request = clientContext.getRequest();
// 如果请求是幂等的,就再次尝试
return !(request instanceof HttpEntityEnclosingRequest);
};
}
/**
* 默认请求配置
*
* @return
*/
private static RequestConfig requestConfig() {
// 创建Http请求配置参数
return RequestConfig.custom()
// 请求超时时间
.setConnectionRequestTimeout(CONNECTION_REQUEST_TIMEOUT)
// 获取连接超时时间
.setConnectTimeout(CONNECT_TIMEOUT)
// 响应超时时间
.setSocketTimeout(SOCKET_TIMEOUT)
.build();
}
/**
* 连接池管理
*
* @return
*/
private static PoolingHttpClientConnectionManager poolingHttpClientConnectionManager() {
Registry<ConnectionSocketFactory> registry = RegistryBuilder
.<ConnectionSocketFactory>create()
.register("http", new PlainConnectionSocketFactory())
.register("https", sslConnectionSocketFactory())
.build();
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(registry);
//设置连接池的最大连接数,即整个池子的大小;
cm.setMaxTotal(60);
//设置每一个路由的最大连接数,
// 这里的路由是指IP+PORT,
// 例如连接池大小(MaxTotal)设置为300,路由连接数设置为200(DefaultMaxPerRoute),
// 对于www.a.com与www.b.com两个路由来说,发起服务的主机连接到每个路由的最大连接数(并发数)不能超过200,
// 两个路由的总连接数不能超过300。
cm.setDefaultMaxPerRoute(40);
return cm;
}
/**
* 跳过验证
*
* @return
*/
private static SSLConnectionSocketFactory sslConnectionSocketFactory() {
SSLContextBuilder builder = new SSLContextBuilder();
try {
return new SSLConnectionSocketFactory(builder.build(), new String[]{"TLSv1"}, null, NoopHostnameVerifier.INSTANCE);
} catch (NoSuchAlgorithmException | KeyManagementException e) {
e.printStackTrace();
}
throw new RuntimeException("创建 SSLConnectionSocketFactory 失败");
}
}
}