HttpClient踩坑记录
在最近做的一个项目中,有一个定时任务经常卡死不动,经过查看log排查发现罪魁祸首在这
HttpResponse response = HttpUtil.getHttpClient().execute(post);
程序运行到这里就就不动了,进去getHttpClient()方法查看:
public class HttpUtil
{
private static HttpClient httpclient = null;
private static HttpClient httpsclient = null;
private static final Logger logger = Logger.getLogger(HttpUtil.class);
static
{
RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(48000).setSocketTimeout(30000).build();
httpsclient = HttpClients.custom().setDefaultRequestConfig(requestConfig).build();
httpclient = getSSLHttpClient();
}
public static HttpClient getHttpClient()
{
return httpclient;
}
可以看出这是个单例模式,共用一个HttpClient,http底层协议是基于tcp的,最终实现是通过socket,socket中有两个超时时间,一个是连接超时时间connectTimeout,另一个是连接成功后,多长时间数据没有返回断开连接soTimeOut。接着看getSSLHttpClient()方法中有没有设置超时时间
private static CloseableHttpClient getSSLHttpClient()
{
try
{
SSLContext context = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy()
{
@Override
public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException
{
return true;
}
}).build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(context);
RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(48000).setSocketTimeout(30000)
.build();
return HttpClients.custom().setSSLSocketFactory(sslsf).setDefaultRequestConfig(requestConfig).build();
}
catch (KeyManagementException e)
{
logger.error("ssl context key management Exception {}", e);
throw new RuntimeException("ssl context key management Exception", e);
}
catch (NoSuchAlgorithmException e)
{
logger.error("ssl context get instance faild, no such method {}", e);
throw new RuntimeException("ssl context get instance faild", e);
}
catch (KeyStoreException e)
{
logger.error("ssl context key store Exception {}", e);
throw new RuntimeException("ssl context key store Exception", e);
}
}
从代码中可以看到是有设置超时时间的,这是为什么呢,搞不懂只好百度,发现发现虽然在RequestConfig设置了SocketTimeout,但在最底层设置SoTimeout时并未使用,socket中如果不设置该值则会导致socket连接成功后如果不返回数据,则该socket会一直等待。
既然在RequestConfig中设置无效,那只好在SocketConfig中设置了,加上如下代码即可解决:
SocketConfig socketConfig = SocketConfig.custom().setSoTimeout(20000).build();
HttpClients.custom().setSSLSocketFactory(sslsf) .setDefaultSocketConfig(socketConfig).setDefaultRequestConfig(requestConfig).build();
最终getSSLHttpClient()方法改为如下:
private static CloseableHttpClient getSSLHttpClient()
{
try
{
SSLContext context = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy()
{
@Override
public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException
{
return true;
}
}).build();
SocketConfig socketConfig = SocketConfig.custom().setSoTimeout(20000).build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(context);
RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(48000).setSocketTimeout(30000)
.build();
return HttpClients.custom().setDefaultSocketConfig(socketConfig).setSSLSocketFactory(sslsf)
.setDefaultRequestConfig(requestConfig).build();
}
catch (KeyManagementException e)
{
logger.error("ssl context key management Exception {}", e);
throw new RuntimeException("ssl context key management Exception", e);
}
catch (NoSuchAlgorithmException e)
{
logger.error("ssl context get instance faild, no such method {}", e);
throw new RuntimeException("ssl context get instance faild", e);
}
catch (KeyStoreException e)
{
logger.error("ssl context key store Exception {}", e);
throw new RuntimeException("ssl context key store Exception", e);
}
}