文章

Httpcomponents-client 连接释放逻辑源码研究

研究背景

上篇(Httpcomponents-core 从池中获取连接代码解析)研究中,我们知道,Httpcomponents-client底层维护了一个socket连接池,对于同一个地址,默认只可以建立2个连接。如果连接不及时释放,就会造成连接池中无可用的连接获取,从而导致请求等待(阻塞)。因此,我们需要关注连接池释放的逻辑。

释放方式

一个比较常见的方式就通过EntityUtils类,比如EntityUtils.consume,源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
     * Ensures that the entity content is fully consumed and the content stream, if exists,
     * is closed.
     *
     * @param entity the entity to consume.
     * @throws IOException if an error occurs reading the input stream
     *
     * @since 4.1
     */
    public static void consume(final HttpEntity entity) throws IOException {
        if (entity == null) {
            return;
        }
        if (entity.isStreaming()) {
            final InputStream instream = entity.getContent();
            if (instream != null) {
                instream.close();
            }
        }
    }

源码解析

上述代码,看上去似乎跟连接的释放没什么关系,实际上,这里的InputStream的实现类是httpcomponents-client里封装的, EofSensorInputStream 。而该Inputstream在构造的时候,会添加一个watcher,该watcher会监听流的消费,从而释放连接。4.3版本以后HttpEntity的实现类变为ResponseEntityProxy,其从Entity中获取Content的代码逻辑如下:

1
2
3
4
 @Override
    public InputStream getContent() throws IOException {
        return new EofSensorInputStream(this.wrappedEntity.getContent(), this);
    }

因此,在EofSensorInputStream关闭流的时候会有响应的处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 @Override
    public void close() throws IOException {
        // tolerate multiple calls to close()
        selfClosed = true;
        checkClose();
    }

protected void checkClose() throws IOException {

        final InputStream toCloseStream = wrappedStream;
        if (toCloseStream != null) {
            try {
                boolean scws = true; // should close wrapped stream?
                if (eofWatcher != null) {
                    scws = eofWatcher.streamClosed(toCloseStream);
                }
                if (scws) {
                    toCloseStream.close();
                }
            } finally {
                wrappedStream = null;
            }
        }
    }

可见,在关闭流的时候,会回调watcher的streamCloased方法,在方法中会对连接进行释放:

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
@Override
    public boolean streamClosed(final InputStream wrapped) throws IOException {
        try {
            final boolean open = connHolder != null && !connHolder.isReleased();
            // this assumes that closing the stream will
            // consume the remainder of the response body:
            try {
                if (wrapped != null) {
                    wrapped.close();
                }
                releaseConnection();
            } catch (final SocketException ex) {
                if (open) {
                    throw ex;
                }
            }
        } catch (final IOException ex) {
            abortConnection();
            throw ex;
        } catch (final RuntimeException ex) {
            abortConnection();
            throw ex;
        } finally {
            cleanup();
        }
        return false;
    }

总结

至此,已经基本理解了HttpClient释放连接的方式,就是在他封装的Entity流上,增加一个watcher,只要调用了stream的close方法,就会自动释放连接。所以,你可见到在EntityUtil中的各种消费Entity的函数中都会去主动关闭流。这也是为什么我们强调在使用HttpClient的过程中,即使Response不使用也一定要消费掉,因为不消费会导致连接永远不会释放。最终造成连接池耗尽造成阻塞。

本文由作者按照 CC BY 4.0 进行授权