文章

一文看透 Apache HttpClient 的底层请求执行与 Socket 连接建立流程(源码级解析)

✅ 一文看透 Apache HttpClient 的底层请求执行与 Socket 连接建立流程(源码级解析)

HttpClient 的请求过程非常复杂,但本质上可以拆解为五个关键阶段:

请求执行 → 连接获取 → Socket 建立 → 响应处理 → 连接复用

本文将从 request.execute() 一路向下,带你完整走完这条调用链。


一、整体执行流程总览(大局观)

一条完整请求的生命周期如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
request.execute()
    ↓
获取连接 requestConnection()
    ↓
ConnManager.getConnection()
    ↓
ConnAdapter.open()
    ↓
Socket.connect()
    ↓
发送请求 → 接收响应
    ↓
响应拦截器
    ↓
连接是否复用

后面的所有源码分析,都是围绕这条主线展开。


二、请求失败后的重试与直连重建

当请求发送异常时,HttpClient 并不是立即失败,而是分情况处理:

1
2
3
4
5
6
7
8
9
this.log.debug(ex.getMessage(), ex);
this.log.info("Retrying request");

if (route.getHopCount() == 1) {
    this.log.debug("Reopening the direct connection.");
    managedConn.open(route, context, params);
} else {
    throw ex;
}

✅ 核心逻辑:

情况是否重试
直连(无代理)✅ 允许重新建连
多跳路由(含代理)❌ 直接抛异常

三、响应拦截器执行阶段

在成功收到响应后,进入协议级拦截器处理阶段:

1
2
response.setParams(params);
requestExec.postProcess(response, httpProcessor, context);

这里会执行:

  • gzip 解压
  • header 处理
  • 重定向相关处理

四、连接是否进入复用池(KeepAlive)

1
2
3
4
5
6
reuse = reuseStrategy.keepAlive(response, context);

if (reuse) {
    long duration = keepAliveStrategy.getKeepAliveDuration(response, context);
    managedConn.setIdleDuration(duration, TimeUnit.MILLISECONDS);
}

✅ 这一步决定:

  • 该连接是 被复用
  • 还是 请求结束后直接关闭

五、是否触发重定向(follow-up request)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
RoutedRequest followup = handleResponse(roureq, response, context);

if (followup == null) {
    done = true;
} else {
    if (reuse) {
        HttpEntity entity = response.getEntity();
        if (entity != null) {
            entity.consumeContent();
        }
        managedConn.markReusable();
    } else {
        managedConn.close();
    }

    if (!followup.getRoute().equals(roureq.getRoute())) {
        releaseConnection();
    }

    roureq = followup;
}

✅ 触发场景:

  • 301 / 302
  • 认证跳转
  • 协议跳转

六、响应完成后的最终连接处理

1
2
3
4
5
6
7
8
9
10
11
12
if ((response == null)
    || (response.getEntity() == null)
    || !response.getEntity().isStreaming()) {

    if (reuse) managedConn.markReusable();
    releaseConnection();

} else {
    HttpEntity entity = response.getEntity();
    entity = new BasicManagedEntity(entity, managedConn, reuse);
    response.setEntity(entity);
}

✅ 这一段的核心目的只有一个:

防止连接泄漏


七、获取连接的真正入口:SingleClientConnManager

真正获取连接是从这里开始的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public final ClientConnectionRequest requestConnection(
        final HttpRoute route,
        final Object state) {

    return new ClientConnectionRequest() {

        public void abortRequest() {}

        public ManagedClientConnection getConnection(
                long timeout, TimeUnit tunit) {

            return SingleClientConnManager.this.getConnection(route, state);
        }
    };
}

✅ 这里做了两件事:

  • 返回一个 连接请求对象
  • 真正获取连接在 getConnection()

八、核心连接获取逻辑:getConnection()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public ManagedClientConnection getConnection(HttpRoute route, Object state) {

    if (managedConn != null)
        revokeConnection();

    closeExpiredConnections();

    boolean recreate = false;

    if (!uniquePoolEntry.connection.isOpen()) {
        recreate = true;
    }

    if (recreate)
        uniquePoolEntry = new PoolEntry();

    managedConn = new ConnAdapter(uniquePoolEntry, route);
    return managedConn;
}

✅ 核心职责总结:

功能作用
回收旧连接防止污染
清理超时连接避免假活跃
创建 PoolEntry承载真实连接
创建 ConnAdapter对外暴露操作接口

真正干活的是 PoolEntry + OperatedClientConnection


九、真正建立 TCP 连接:managedConn.open()

1
2
3
4
5
6
7
8
9
10
11
12
13
public void open(HttpRoute route,
                 HttpContext context,
                 HttpParams params) throws IOException {

    this.tracker = new RouteTracker(route);

    connOperator.openConnection(
        this.connection,
        route.getTargetHost(),
        route.getLocalAddress(),
        context, params
    );
}

这里第一次真正触发 网络通信级别的操作


十、最终 Socket 创建与连接(最底层)

最终调用落在:

1
DefaultClientConnectionOperator.openConnection(...)

核心简化逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
InetAddress[] addresses = InetAddress.getAllByName(target.getHostName());

for (int i = 0; i < addresses.length; ++i) {

    Socket sock = socketFactory.createSocket();
    conn.opening(sock, target);

    try {
        Socket connsock = socketFactory.connectSocket(
            sock,
            addresses[i].getHostAddress(),
            target.getPort(),
            local,
            0,
            params
        );

        prepareSocket(connsock, context, params);
        conn.openCompleted(isSecure, params);
        break;

    } catch (Exception ex) {
        if (i == addresses.length - 1) {
            throw new HttpHostConnectException(target, ex);
        }
    }
}

✅ 这一段完成了真正的底层动作:

步骤说明
DNS 解析getAllByName
创建 SocketcreateSocket()
TCP 连接connectSocket()
SSL 握手HTTPS 时
连接完成回调openCompleted()

从这一刻开始,HTTP 请求才真正拥有了底层网络通道。


✅ 结语:你真正该记住的 5 层核心结构

1
2
3
4
5
6
7
HttpClient 请求五层结构:

[1] RequestExecutor      → 请求调度
[2] ConnManager          → 连接管理
[3] ConnAdapter          → 连接适配
[4] PoolEntry            → 物理连接池
[5] Socket               → 真实网络通信
本文由作者按照 CC BY 4.0 进行授权