Apache HTTPClient 源码解析:主流程

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

使用示例

最近使用 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 时需要注意几点:

  1. 设置合理的超时时间:连接超时、读取超时最常见,容易被忽略的是从连接池中获取连接的超时时间。
  2. 设置合理的连接池大小:连接池大小和读取耗时、QPS 有关,一般等于峰值 QPS * 耗时(单位是秒)。
  3. 设置合理的长连接有效时间:使用连接池时,默认就使用了长连接,长连接有效时间应该和服务端的长连接有效时间保持一致。如果客户端设置的有效时间过长,则会在服务端连接断开时而客户端依然去请求时导致 NoHttpResponseException。也可以通过设置 RequestConfig.setStaleConnectionCheckEnabled 参数让客户端每次请求之前都检查长连接有效性,但是这样会导致性能的下降。之前在关于长连接里也提到过一些注意点。
  4. 设置合理的重试策略:合理的重试,可以提升应用的可用性。默认的重试策略不会对超时进行重试,然而超时是十分从常见的问题,服务端异常或网络抖动都可能导致超时。这里我自定义重试策略如下所示:
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
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值