1. 概述

本文将深入讲解 Apache HttpClient 5 的超时配置方案。想系统学习 HttpClient 的其他高级用法?推荐阅读 **HttpClient 核心教程**。

2. 使用 HttpClient 5.x API 配置超时

新版本 API 提供了更灵活的超时配置方式。我们通过 ConnectionConfig 设置连接和套接字超时:

ConnectionConfig connConfig = ConnectionConfig.custom()
    .setConnectTimeout(timeout, TimeUnit.MILLISECONDS)
    .setSocketTimeout(timeout, TimeUnit.MILLISECONDS)
    .build();

接下来创建连接管理器并绑定配置:

BasicHttpClientConnectionManager cm = new BasicHttpClientConnectionManager();
cm.setConnectionConfig(connConfig);

连接管理器的详细配置可参考:Apache HttpClient 连接管理

3. 使用 HttpClient 4.3 配置超时

在 HttpClient 4.3 中,推荐使用 流式构建器 API 统一设置超时:

int timeout = 5;
RequestConfig config = RequestConfig.custom()
  .setConnectTimeout(timeout * 1000)
  .setConnectionRequestTimeout(timeout * 1000)
  .setSocketTimeout(timeout * 1000).build();
CloseableHttpClient client = 
  HttpClientBuilder.create().setDefaultRequestConfig(config).build();

优势:类型安全且可读性高,三重超时配置一气呵成。

4. 超时属性详解

三种核心超时参数的含义:

超时类型 参数名 作用场景
连接超时 http.connection.timeout 建立远程主机连接的最大耗时
套接字超时 http.socket.timeout 连接建立后,两个数据包间的最大无数据间隔
连接管理器超时 http.connection-manager.timeout 从连接池获取连接的最大等待时间

⚠️ 关键点

  • 前两种超时直接影响网络交互稳定性
  • 第三种在高并发场景下尤为重要(防止连接池饥饿)

5. 使用 HttpClient 执行请求

配置完成后即可发起 HTTP 请求:

final HttpGet request = new HttpGet("http://www.github.com");

try (CloseableHttpClient client = HttpClientBuilder.create()
    .setDefaultRequestConfig(requestConfig)
    .setConnectionManager(cm)
    .build();

    CloseableHttpResponse response = (CloseableHttpResponse) client
        .execute(request, new CustomHttpClientResponseHandler())) {

    final int statusCode = response.getCode();
    assertThat(statusCode, equalTo(HttpStatus.SC_OK));
}

超时效果

  • 连接建立阶段:5 秒超时 → 抛出 ConnectTimeoutException
  • 数据传输阶段:5 秒无数据 → 抛出 SocketTimeoutException

6. 硬超时实现

常规超时无法覆盖整个请求总耗时(如大文件下载)。解决方案:基于 TimerTask 实现强制中断:

HttpGet getMethod = new HttpGet("http://localhost:8082/api/bars/1");
getMethod.setConfig(requestConfig);

int hardTimeout = 5000; // 毫秒
TimerTask task = new TimerTask() {
    @Override
    public void run() {
        getMethod.abort(); // 硬中断
    }
};
new Timer(true).schedule(task, hardTimeout);

try (CloseableHttpClient client = HttpClientBuilder.create()
    .setDefaultRequestConfig(requestConfig)
    .setConnectionManager(cm)
    .build();

    CloseableHttpResponse response = (CloseableHttpResponse) client
        .execute(getMethod, new CustomHttpClientResponseHandler())) {

    System.out.println("HTTP状态码: " + response.getCode());
}

原理:定时器在指定时间后强制中断请求线程,确保总时长可控。

7. 超时与 DNS 轮询的坑

大型域名常采用 DNS 轮询策略(单域名映射多 IP)。这会导致超时计算出现偏差:

graph TD
    A[获取IP列表] --> B[尝试IP1超时]
    B --> C[尝试IP2超时]
    C --> D[尝试IP3超时]
    D --> E[总耗时=所有IP超时之和]

问题表现:实际超时时间 = 单IP超时 × IP数量,远超预期值。⚠️ 且默认日志级别下该行为对开发者透明。

复现代码

ConnectionConfig connConfig = ConnectionConfig.custom()
    .setConnectTimeout(1000, TimeUnit.MILLISECONDS)
    .setSocketTimeout(1000, TimeUnit.MILLISECONDS)
    .build();

RequestConfig requestConfig = RequestConfig.custom()
    .setConnectionRequestTimeout(Timeout.ofMilliseconds(3000))
    .build();

BasicHttpClientConnectionManager cm = new BasicHttpClientConnectionManager();
cm.setConnectionConfig(connConfig);

CloseableHttpClient client = HttpClientBuilder.create()
    .setDefaultRequestConfig(requestConfig)
    .setConnectionManager(cm)
    .build();

HttpGet request = new HttpGet("http://www.google.com:81");
client.execute(request, new CustomHttpClientResponseHandler());

DEBUG 日志输出:

DEBUG o.a.h.i.c.HttpClientConnectionOperator - 尝试连接 www.google.com/173.194.34.212:81
DEBUG o.a.h.i.c.HttpClientConnectionOperator - 连接超时,将尝试下一个IP

DEBUG o.a.h.i.c.HttpClientConnectionOperator - 尝试连接 www.google.com/173.194.34.208:81
DEBUG o.a.h.i.c.HttpClientConnectionOperator - 连接超时,将尝试下一个IP
// ...

8. 总结

本文系统梳理了 HttpClient 的超时配置方案:

  1. 基础超时:连接/套接字/连接池三重防护
  2. 硬超时:通过定时中断实现总时长控制
  3. DNS轮询陷阱:实际超时可能远超配置值

完整代码示例见:GitHub 项目,HttpClient 4 版本在 apache-httpclient4 模块


原始标题:Apache HttpClient Timeout