使用示例
最近使用 HTTPClient 踩了不少坑,故在此总结下。本文基于 HTTPClient 4.5.6 进行分析,分析源码之前,先贴下用法示例:
public class HttpUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(HttpUtil.class);
private static final int MAX_TIMEOUT = 400;
private static final CloseableHttpClient httpClient;
static {
// 自定义 SSL 策略
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", createSSLConnSocketFactory())
.build();
// 设置连接池
PoolingHttpClientConnectionManager connMgr = new PoolingHttpClientConnectionManager(registry);
connMgr.setMaxTotal(100); // 设置连接池大小
connMgr.setDefaultMaxPerRoute(connMgr.getMaxTotal());
connMgr.setValidateAfterInactivity(60000); // 设置长连接
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(MAX_TIMEOUT) // 连接超时
.setSocketTimeout(MAX_TIMEOUT) // 传输超时
.setConnectionRequestTimeout(MAX_TIMEOUT) // 设置从连接池获取连接实例的超时
.build();
httpClient = HttpClients.custom()
.setConnectionManager(connMgr)
.setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy())
.setRetryHandler(new MyHttpRequestRetryHandler()) // 重试 1 次
.setDefaultRequestConfig(requestConfig)
.build();
}
public static byte[] doGet(String url) throws IOException {
HttpGet httpGet = new HttpGet(url);
CloseableHttpResponse response = null;
try {
response = httpClient.execute(httpGet);
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode != HttpStatus.SC_OK) {
String message = EntityUtils.toString(response.getEntity());
throw new HttpResponseException(statusCode, message);
}
byte[] bytes = EntityUtils.toByteArray(response.getEntity());
return bytes;
} catch (Exception e) {
LOGGER.error("doGet error, url: {}", url, e);
throw e;
} finally {
if (response != null) {
try {
EntityUtils.consume(response.getEntity());
} catch (IOException e) {
}
}
}
}
private static SSLConnectionSocketFactory createSSLConnSocketFactory() {
SSLConnectionSocketFactory sslsf = null;
try {
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
return true;
}
}).build();
sslsf = new SSLConnectionSocketFactory(sslContext, new HostnameVerifier() {
@Override
public boolean verify(String s, SSLSession sslSession) {
return true; // 信任所有证书
}
});
} catch (GeneralSecurityException e) {
LOGGER.error("createSSLConnSocketFactory error", e);
throw Throwables.propagate(e);
}
return sslsf;
}
}
使用 HTTPClient 时需要注意几点:
- 设置合理的超时时间:连接超时、读取超时最常见,容易被忽略的是从连接池中获取连接的超时时间。
- 设置合理的连接池大小:连接池大小和读取耗时、QPS 有关,一般等于峰值 QPS * 耗时(单位是秒)。
- 设置合理的长连接有效时间:使用连接池时,默认就使用了长连接,长连接有效时间应该和服务端的长连接有效时间保持一致。如果客户端设置的有效时间过长,则会在服务端连接断开时而客户端依然去请求时导致 NoHttpResponseException。也可以通过设置 RequestConfig.setStaleConnectionCheckEnabled 参数让客户端每次请求之前都检查长连接有效性,但是这样会导致性能的下降。之前在关于长连接里也提到过一些注意点。
- 设置合理的重试策略:合理的重试,可以提升应用的可用性。默认的重试策略不会对超时进行重试,然而超时是十分从常见的问题,服务端异常或网络抖动都可能导致超时。这里我自定义重试策略如下所示:
public class MyHttpRequestRetryHandler extends DefaultHttpRequestRetryHandler {
public MyHttpRequestRetryHandler() {
super(1, false, Arrays.asList(
UnknownHostException.class,
SSLException.class)); // 遇到这两种异常时不重试
}
}
源码分析
让我们从 httpClient.execute(httpGet) 开始看代码如何执行的吧:
public CloseableHttpResponse execute(HttpUriRequest request) throws IOException, ClientProtocolException {
return this.execute(request, (HttpContext)null);
}
public CloseableHttpResponse execute(HttpUriRequest request, HttpContext context) throws IOException, ClientProtocolException {
Args.notNull(request, "HTTP request");
return this.doExecute(determineTarget(request), request, context);
}
protected abstract CloseableHttpR

本文详细介绍了Apache HTTPClient 4.5.6的使用示例,强调了设置超时时间、连接池大小、长连接有效时间和重试策略的重要性。在源码分析部分,从`httpClient.execute(httpGet)`开始,逐步深入到`CloseableHttpClient`的`doExecute`方法,解析了RedirectExec、RetryExec、ProtocolExec和MainClientExec等执行链中的关键角色,最后在HttpRequestExecutor中完成HTTP请求和响应的处理。
最低0.47元/天 解锁文章
2933

被折叠的 条评论
为什么被折叠?



