HttpClient源码解析系列:第四篇:Connection是怎么生成和管理的

时间:2024-05-18 22:02:50
上面介绍了HttpClientConnection是最核心的接口
HttpClient源码解析系列:第四篇:Connection是怎么生成和管理的
这个接口的使用分布在上图所示的类或者接口中。图中可以看出,分成四个大类:Connection的不同实现,Manager,Operator,Factory。
由于这里不着重考察Connection的实现,所以略过,而考察Manager,Operator,Factory。
经过简单的代码筛查,可以发现在管理HttpClientConnection过程中,它以一个子接口:ManagedHttpClientConnection的形式存在。
这个很容易理解,HttpClientConnection的实例并不是用户new出来的,而是经由Manager管理的,所以衍生出一个Managed的接口。
此外,Operator和Factory也是经由Manager来调用的,所以可以先从 HttpClientConnectionManager 来切入代码。

接口 HttpClientConnectionManager 有两个实现
HttpClient源码解析系列:第四篇:Connection是怎么生成和管理的
HttpClient源码解析系列:第四篇:Connection是怎么生成和管理的
显然,BasicHttpClientConnectionManager 是最基本的可用版本。那么我们来看一下其中的代码。
HttpClient源码解析系列:第四篇:Connection是怎么生成和管理的
从其中的参数可以看到,这里面有一个 Connection和一个Route,也就是说这个基础版的Manager只能管理一个Connection。
HttpClient源码解析系列:第四篇:Connection是怎么生成和管理的
来看最核心的方法:getConnection, connect
synchronized HttpClientConnection getConnection(final HttpRoute route, final Object state) {
        Asserts.check(!this.isShutdown.get(), "Connection manager has been shut down");
        if (this.log.isDebugEnabled()) {
            this.log.debug("Get connection for route " + route);
        }
        Asserts.check(!this.leased, "Connection is still allocated");
        if (!LangUtils.equals(this.route, route) || !LangUtils.equals(this.state, state)) {
            closeConnection();
        }
        this.route = route; //创建链接的时候目标主机的信息是一定要的
        this.state = state;
        checkExpiry(); //校验是否存在
        if (this.conn == null) {
            this.conn = this.connFactory.create(route, this.connConfig); // 这个Manager管理唯一一个Connection,如果获取连接的时候不存在则新建一个。
        }
        this.leased = true;
        return this.conn;
    }

    @Override
    public void connect( final HttpClientConnection conn, final HttpRoute route, final int connectTimeout, final HttpContext context ) throws IOException {
        Args.notNull(conn, "Connection");
        Args.notNull(route, "HTTP route");
        Asserts.check(conn == this.conn, "Connection not obtained from this manager");
        final HttpHost host;
        if (route.getProxyHost() != null) {
            host = route.getProxyHost();
        } else {
            host = route.getTargetHost();
        }
        final InetSocketAddress localAddress = route.getLocalSocketAddress();
        this.connectionOperator.connect(this.conn, host, localAddress,
                connectTimeout, this.socketConfig, context); // 最终还是依赖Operator来连接, context是外面传进来的
    }
从代码可以看出来,Connection的生成管理连接释放都是通过Manager来完成的,这样就让Connection只需要维持核心逻辑即:发送Response和接受Response(参考HttpClientConnection接口)。

但是,最核心的连接过程最终还是通过ConnectionOperator来完成的,下面来看HttpClientConnecitonOperator,这个接口就只有一个实现,即DefaultHttpClientConnectionOperator
这个类很简单的,先看看最核心的 connect方法。

@Override
    public void connect(
            final ManagedHttpClientConnection conn,
            final HttpHost host,
            final InetSocketAddress localAddress,
            final int connectTimeout,
            final SocketConfig socketConfig,
            final HttpContext context) throws IOException {
        final Lookup<ConnectionSocketFactory> registry = getSocketFactoryRegistry(context);
        final ConnectionSocketFactory sf = registry.lookup(host.getSchemeName()); // 从Context中获取SocketFactory,可以看到,根据Scheme(是否是Https)不一样会有不同。这里看到Context是做什么用的。
        if (sf == null) {
            throw new UnsupportedSchemeException(host.getSchemeName() +
                    " protocol is not supported");
        }
        final InetAddress[] addresses = host.getAddress() != null ?
                new InetAddress[] { host.getAddress() } : this.dnsResolver.resolve(host.getHostName());
        final int port = this.schemePortResolver.resolve(host);
        for (int i = 0; i < addresses.length; i++) {
            final InetAddress address = addresses[i];
            final boolean last = i == addresses.length - 1;

            Socket sock = sf.createSocket(context);
            sock.setSoTimeout(socketConfig.getSoTimeout());
            sock.setReuseAddress(socketConfig.isSoReuseAddress());
            sock.setTcpNoDelay(socketConfig.isTcpNoDelay());
            sock.setKeepAlive(socketConfig.isSoKeepAlive());
            if (socketConfig.getRcvBufSize() > 0) {
                sock.setReceiveBufferSize(socketConfig.getRcvBufSize());
            }
            if (socketConfig.getSndBufSize() > 0) {
                sock.setSendBufferSize(socketConfig.getSndBufSize());
            }

            final int linger = socketConfig.getSoLinger();
            if (linger >= 0) {
                sock.setSoLinger(true, linger);
            }
            conn.bind(sock);

            final InetSocketAddress remoteAddress = new InetSocketAddress(address, port);
            if (this.log.isDebugEnabled()) {
                this.log.debug("Connecting to " + remoteAddress);
            }
            try {
                sock = sf.connectSocket(
                        connectTimeout, sock, host, remoteAddress, localAddress, context);
                conn.bind(sock); // connect 的过程,也就是新建Socket并绑定到HttpClientConnection上的过程。
                if (this.log.isDebugEnabled()) {
                    this.log.debug("Connection established " + conn);
                }
                return;
            } catch (final SocketTimeoutException ex) {
                if (last) {
                    throw new ConnectTimeoutException(ex, host, addresses);
                }
            } catch (final ConnectException ex) {
                if (last) {
                    final String msg = ex.getMessage();
                    if ("Connection timed out".equals(msg)) {
                        throw new ConnectTimeoutException(ex, host, addresses);
                    } else {
                        throw new HttpHostConnectException(ex, host, addresses);
                    }
                }
            } catch (final NoRouteToHostException ex) {
                if (last) {
                    throw ex;
                }
            }
            if (this.log.isDebugEnabled()) {
                this.log.debug("Connect to " + remoteAddress + " timed out. " +
                        "Connection will be retried using another IP address");
            }
        }
    }
ConnectionSocketFactory也只有两个实现:PlainConnectionSocketFactory,SSLConnectionSocketFactory(实现了LayeredConnectionSocketFactory,继承自ConnectionSocketFactory)。
PlainConnectionSocketFactory是用于创建非HTTPS连接的,代码很简单,本质上就是 new Socket(); (关于Socket编程,是Java基础内容,不解释)
而SSLConnectionSocketFactory就是在创建Socket前后有很多SSL相关的操作,这里不详细说明了。
    通过代码可以看到, ConnectionOperator 本身只做一件事情,就是处理Connection和Socket的关系,这也是Connection管理的一部分,简单点来说直接集成到Manager中也是可以的。为什么要单独抽离这一部分,按照我的理解,由于这个关系中可能出现 Http 和 HTTPs 的不同处理,不是所有人都有编写的能力。常规的业务代码,开发团队设计好就可以了,但是对于应对规范的(如HTTPS规范),则需要特定的领域专家来完成。

上面两个部分:Manager和Operator是对现成的HttpClientConnection进行管理,下面来看HttpClientConnection怎么生成的。核心的接口就是HttpConnectionFactory。
其实现就两个,DefaultBHttpClientConnectionFactory 和 ManagedHttpClientConnectionFactory。实现上都差不多,前者是默认的,后者是生成 ManageredHttpClientConnection。由于我们在这里是通过Manager来管理的,所以重点看 ManageredHttpClientConnection,Default那个差不多的
@Override
    public ManagedHttpClientConnection create(final HttpRoute route, final ConnectionConfig config) {
        final ConnectionConfig cconfig = config != null ? config : ConnectionConfig.DEFAULT;
        CharsetDecoder chardecoder = null;
        CharsetEncoder charencoder = null;
        final Charset charset = cconfig.getCharset();
        final CodingErrorAction malformedInputAction = cconfig.getMalformedInputAction() != null ?
                cconfig.getMalformedInputAction() : CodingErrorAction.REPORT;
        final CodingErrorAction unmappableInputAction = cconfig.getUnmappableInputAction() != null ?
                cconfig.getUnmappableInputAction() : CodingErrorAction.REPORT;
        if (charset != null) {
            chardecoder = charset.newDecoder();
            chardecoder.onMalformedInput(malformedInputAction);
            chardecoder.onUnmappableCharacter(unmappableInputAction);
            charencoder = charset.newEncoder();
            charencoder.onMalformedInput(malformedInputAction);
            charencoder.onUnmappableCharacter(unmappableInputAction);
        }
        final String id = "http-outgoing-" + Long.toString(COUNTER.getAndIncrement());
        return new LoggingManagedHttpClientConnection(
                id,
                log,
                headerlog,
                wirelog,
                cconfig.getBufferSize(),
                cconfig.getFragmentSizeHint(),
                chardecoder,
                charencoder,
                cconfig.getMessageConstraints(),
                incomingContentStrategy,
                outgoingContentStrategy,
                requestWriterFactory,
                responseParserFactory);
    }
LoggingManagedHttpClientConnection extends DefaultManagedHttpClientConnection  extendsDefaultBHttpClientConnection
这在前面讲过了,也就是核心的,HttpClientConnection 怎样利用 Socket 来发送Request以及接收Response。

到这里,就吧BasicHttpClientManager的核心代码看完了。对于PoolingHttpClientConnectionManager, 涉及到对象池化的概念。、
在常用的Apache Commons Pool 中可以是这样定义的:
    PoolableObjectFactory用于管理被池化的对象的产生、**、挂起、校验和销毁;
    ObjectPool用于管理要被池化的对象的借出和归还,并通知PoolableObjectFactory完成相应的工作;

     我们来看看PoolingHttpClientConnectionManager是如何来定义对象池的。正常的Pool管理中有一个Factory,从代码中可以看出来,是InternalConnectionFactory。同时还有一个ObjectPool,在这里是CPool,但是它基本是个空的封装,继承自AbstractConnPool。 其实PoolingHttpClientConnectionManager看上去结构简单,也能做很多事情,但是最终还是落到了CPool上。而CPool又落到了AbstractConnPool上。

    AbstractConnPool的代码详细分析,暂时不讲了,理解核心的逻辑就可以了。


HttpClient源码解析系列:第四篇:Connection是怎么生成和管理的
图中还有一个重要的接口没有表达出来:ManagedHttpClientConnection。跟图中很多类有关联关系,因为它是最终生成的结果。所以如果加到图上,那这个图就成了一个网状结构了。
只需要知道,这个接口贯穿整个过程就可以了。