上一个文章说了http连接的缓存池,以及借出的过程,借出使用的是HttoRoute,先根据route定位到RouteSpecificPool,然后从RouteSpecificPool中借出一个httpConnection,借出后都是用一个Entry包装的,entry里面含有httpRouter+httpConnection。这一篇说一下借出之前的过程以及借出后的使用操作。
缓存的对象是CPool,他的使用是在HttpClientConnectionManager里面,这个类从他的名字就能看出来是管理HttpConnection的,他的实现类一般都是用的PoolingHttpClientConnectionManager,即使用缓存的httpConnection管理器,这个类的里面就会用到了CPool类,在创建一个PoolingHttpClientConnectionManager的时候可以发现:
public PoolingHttpClientConnectionManager( final HttpClientConnectionOperator httpClientConnectionOperator, final HttpConnectionFactoryconnFactory, final long timeToLive, final TimeUnit tunit) { super(); this.configData = new ConfigData();//这个是一些配置参数,都是用于socket连接以及http通信的参数,后面会详细写 this.pool = new CPool(new InternalConnectionFactory(this.configData, connFactory), 2, 20, timeToLive, tunit);//如果没有传递一个CPool,则要生产一个默认的,默认的里面会会设置每个Route最多可以有两个connection,一个CPool最多可以开10个connection,第一个参数是用于创建一个httpConnection的,暂时先忽略第一个参数 this.pool.setValidateAfterInactivity(5000);//设置校验的最大时间,上一篇文章中说了如果一个PoolEnty的updateTime + 校验时间<当前时间,就要检查一下是否可以,如果没有设置校验时间默认是5秒。 this.connectionOperator = Args.notNull(httpClientConnectionOperator, "HttpClientConnectionOperator");//ConnectionOperato是管理具体的http连接的,比如使用schema解析端口,dns确定ip,然后将本地一个socket和远端的socket进行连接 this.isShutDown = new AtomicBoolean(false); } /** * Visible for test. */ PoolingHttpClientConnectionManager( final CPool pool,//也可以指定一个CPool,这样就不会那些配置很低的连接了 final Lookup socketFactoryRegistry,//这个类更简单,是用于确定socket类型的,如果是http的就是创建一个socket,如果是https的就会创建一个加密的socket. final SchemePortResolver schemePortResolver, final DnsResolver dnsResolver) { super(); this.configData = new ConfigData(); this.pool = pool; this.connectionOperator = new DefaultHttpClientConnectionOperator(socketFactoryRegistry, schemePortResolver, dnsResolver);//功能同上 this.isShutDown = new AtomicBoolean(false); }
看完了构造方法可以发现他里面创建了一个CPool,并更加熟悉了CPool的配置,CPool里面含有一个connectionFactory,用于创建http连接,不过这个过于底层,直接和http的请求的细节相关联,所以我没有看,而且不影响应用层的理解;在创建了CPool之后还可以使用方法修改pool的配置,比如;
@Override public void setMaxTotal(final int max) {//修改总的连接的爽 this.pool.setMaxTotal(max); } @Override public void setDefaultMaxPerRoute(final int max) {//修改默认的route对应的httpConnection的数量,如果某个route没有配置的话 this.pool.setDefaultMaxPerRoute(max); } @Override public void setMaxPerRoute(final HttpRoute route, final int max) {//指定某个route最大的connection的数量 this.pool.setMaxPerRoute(route, max); }
既然这个类管理了一个CPool,那么借出connection的方法就在这个类里面了,方法是:requestConnection
public ConnectionRequest requestConnection(final HttpRoute route,final Object state) {//根据httpRoute借出一个,返回的并不是一个PoolEnty或者是connection,而是用一个ConnectionReuqet封装了, Args.notNull(route, "HTTP route"); final Futurefuture = this.pool.lease(route, state, null);//从pool中借出一个,此方法可能会借出一个,也可能等待,等待后可能最终会借出一个,也可能是超时。 return new ConnectionRequest() { @Override public boolean cancel() { return future.cancel(true); } @Override public HttpClientConnection get(final long timeout,final TimeUnit tunit) throws InterruptedException, ExecutionException, ConnectionPoolTimeoutException {//再次调用get方法才会获得最终的connection,里面会调用leaseConnection方法 return leaseConnection(future, timeout, tunit); } }; } protected HttpClientConnection leaseConnection(final Future future, final long timeout,final TimeUnit tunit) throws InterruptedException, ExecutionException, ConnectionPoolTimeoutException { final CPoolEntry entry; try { entry = future.get(timeout, tunit); if (entry == null || future.isCancelled()) { throw new InterruptedException(); } Asserts.check(entry.getConnection() != null, "Pool entry with no connection"); return CPoolProxy.newProxy(entry);//这个方法将借出的CpoolEntry进行了封装,封装为一个HttpClientConnection, } catch (final TimeoutException ex) { throw new ConnectionPoolTimeoutException("Timeout waiting for connection from pool"); } }
对于连接的获取是在 ClientExecChain里面,httpClient自带了很多的exec,也就是http请求的发起对象,我就看了一个最简单的:MinimalClientExec,这个是最简单的http请求的执行器,仅仅执行最基础的操作,不会进行失败重连,如果有错误也不会处理而是抛出来,对于这个类和httpConnection、httpConnectionFactory一样,不要过多的去深究,因为内部的网络通信和http的具体协议都在这里面了,对于我们的应用层是没有必要知道这些的。仅仅看看他的exec方法,这个方法就是执行http请求的方法
@Override public CloseableHttpResponse execute(final HttpRoute route,final HttpRequestWrapper request,final HttpClientContext context,final HttpExecutionAware execAware) throws IOException, HttpException { rewriteRequestURI(request, route); final ConnectionRequest connRequest = connManager.requestConnection(route, null);//从连接池里面获得connection if (execAware != null) {//不用看,没懂什么意思,但是不妨碍应用层面的了解 if (execAware.isAborted()) { connRequest.cancel(); throw new RequestAbortedException("Request aborted"); } else { execAware.setCancellable(connRequest); } } final RequestConfig config = context.getRequestConfig();//获得这次请求的配置,这里面含有三个时间,下面有 final HttpClientConnection managedConn; try { final int timeout = config.getConnectionRequestTimeout();//从连接池中获得connection的超时时间 managedConn = connRequest.get(timeout > 0 ? timeout : 0, TimeUnit.MILLISECONDS); } catch(final InterruptedException interrupted) { Thread.currentThread().interrupt(); throw new RequestAbortedException("Request aborted", interrupted); } catch(final ExecutionException ex) { Throwable cause = ex.getCause(); if (cause == null) { cause = ex; } throw new RequestAbortedException("Request execution failed", cause); } final ConnectionHolder releaseTrigger = new ConnectionHolder(log, connManager, managedConn);//用一个对象封装connection,这个对象增加了是否可用以及可用时间的功能,以方便重用这个connection,所以起名叫做releaseTrigger try { if (execAware != null) {// if (execAware.isAborted()) { releaseTrigger.close(); throw new RequestAbortedException("Request aborted"); } else { execAware.setCancellable(releaseTrigger); } } if (!managedConn.isOpen()) {//刚创建出来的connection是没有open的,没有关联socket,此时要建立tcp连接 final int timeout = config.getConnectTimeout();//建立连接的超时时间 this.connManager.connect(managedConn,route,timeout > 0 ? timeout : 0,context);//这个地方就用到了连接的方法,下面有介绍 this.connManager.routeComplete(managedConn, route, context);//标志建立连接完成了 } final int timeout = config.getSocketTimeout();//socket的超时时间 if (timeout >= 0) { managedConn.setSocketTimeout(timeout); } //下面这一堆是用于发送http请求用的,不涉及应用层面,所以不用看 HttpHost target = null; final HttpRequest original = request.getOriginal(); if (original instanceof HttpUriRequest) { final URI uri = ((HttpUriRequest) original).getURI(); if (uri.isAbsolute()) { target = new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme()); } } if (target == null) { target = route.getTargetHost(); } context.setAttribute(HttpCoreContext.HTTP_TARGET_HOST, target); context.setAttribute(HttpCoreContext.HTTP_REQUEST, request); context.setAttribute(HttpCoreContext.HTTP_CONNECTION, managedConn); context.setAttribute(HttpClientContext.HTTP_ROUTE, route); httpProcessor.process(request, context); final HttpResponse response = requestExecutor.execute(request, managedConn, context); httpProcessor.process(response, context); // The connection is in or can be brought to a re-usable state. if (reuseStrategy.keepAlive(response, context)) {//这里就看到了1.0和1.1的区别,1.1是keepalive的,而1.0则不是 // Set the idle duration of this connection final long duration = keepAliveStrategy.getKeepAliveDuration(response, context);//判断剩余的时间是多少,从他的具体实现来看是查看了一下一个叫做timeout的heade来确认的,所以我猜测是由服务端确定的这个数字。 releaseTrigger.setValidFor(duration, TimeUnit.MILLISECONDS);//标记这个connection在多少时间内不需要校验。 releaseTrigger.markReusable();//设置为可重用 } else {//如果是1.0的,则标记这个连接是不可复用的 releaseTrigger.markNonReusable(); } // check for entity, release connection if possible final HttpEntity entity = response.getEntity(); if (entity == null || !entity.isStreaming()) {//这里的isSteaming我一直没有理解,难不成有的http不是基于stream的?我做过的试验都不会进入这个方法。如果有人知道这里的原因,请联系我,我的qq是:1308567317 // connection not needed and (assumed to be) in re-usable state releaseTrigger.releaseConnection(); return new HttpResponseProxy(response, null); } else {//我做的试验都是进入这个,返回的这个代理对象增加了一些功能,比如在关闭inpustream的时候可以自动释放连接。下一篇会看这个对象。 return new HttpResponseProxy(response, releaseTrigger); } } catch (final ConnectionShutdownException ex) { final InterruptedIOException ioex = new InterruptedIOException( "Connection has been shut down"); ioex.initCause(ex); throw ioex; } catch (final HttpException ex) { releaseTrigger.abortConnection(); throw ex; } catch (final IOException ex) { releaseTrigger.abortConnection(); throw ex; } catch (final RuntimeException ex) { releaseTrigger.abortConnection(); throw ex; } }
上面说了如何使用connection的,大致的逻辑是先借一个,然后判断其有没有连接socket(如果是新建的就没有),如果没有就要调用connManager.connect(),即将当前的connection绑定到一个socket上。这个方法还是很关键的,看下PoolingHttpClientConnectionManager.connect方法
@Override public void connect(final HttpClientConnection managedConn,final HttpRoute route,final int connectTimeout,final HttpContext context) throws IOException {//给一个connection添加底层的socket连接,第一个参数是要添加socket的connection,第二个是route,第三个则是指定的tcp连接的连接超时时间。 final ManagedHttpClientConnection conn; synchronized (managedConn) { final CPoolEntry entry = CPoolProxy.getPoolEntry(managedConn);//因为借出去的是一个代理,所以要从代理里面获得里面的代理对西乡 conn = entry.getConnection(); } final HttpHost host; if (route.getProxyHost() != null) { host = route.getProxyHost(); } else { host = route.getTargetHost(); } final InetSocketAddress localAddress = route.getLocalSocketAddress(); SocketConfig socketConfig = this.configData.getSocketConfig(host);//获得socket的配置,比如缓冲大小、是否复用、linger时间 if (socketConfig == null) { socketConfig = this.configData.getDefaultSocketConfig(); } if (socketConfig == null) { socketConfig = SocketConfig.DEFAULT; } this.connectionOperator.connect(conn, host, localAddress, connectTimeout, socketConfig, context); }
上面的连接方法是委托给了 DefaultHttpClientConnectionOperator connectionOperator属性,这个DefaultHttpClientConnectionOperator 类就是将创建的httpConnection和某个socket关联起来,因为http最终还是要使用tcp连接,使用socket,那一块的功能代码都在这个类里面了。
看一下这个类的重要方法:
public class DefaultHttpClientConnectionOperator implements HttpClientConnectionOperator { static final String SOCKET_FACTORY_REGISTRY = "http.socket-factory-registry"; private final Log log = LogFactory.getLog(getClass()); private final LookupsocketFactoryRegistry; private final SchemePortResolver schemePortResolver; private final DnsResolver dnsResolver; public DefaultHttpClientConnectionOperator(final Lookup socketFactoryRegistry, final SchemePortResolver schemePortResolver,final DnsResolver dnsResolver) { super(); Args.notNull(socketFactoryRegistry, "Socket factory registry"); this.socketFactoryRegistry = socketFactoryRegistry;//用于获得socketFactory,根据schema获得,如果是http的就返回一个能创建socket的registry,如果是https的就返回一个能创建加密socket的registry this.schemePortResolver = schemePortResolver != null ? schemePortResolver :DefaultSchemePortResolver.INSTANCE;//在连接的时候要指定端口,这个用于根据schema确定端口 this.dnsResolver = dnsResolver != null ? dnsResolver : SystemDefaultDnsResolver.INSTANCE;//用于根据dns确定ip } @SuppressWarnings("unchecked") private Lookup getSocketFactoryRegistry(final HttpContext context) { Lookup reg = (Lookup ) context.getAttribute(SOCKET_FACTORY_REGISTRY); if (reg == null) { reg = this.socketFactoryRegistry; } return reg; } //用于将创建的httpConnection和socket关联起来,因为http最终还是要靠tcp连接的,所以还是要使用socket,刚刚创建的httpConnection的isOpen返回的额是false,此时就要进行connect,进入这个方法, public void connect(final ManagedHttpClientConnection conn,//创建的连接,注意这个连接只是制定了连接了具体参数和怎么连接,并没有底层的socket,使用socket需要经过这个方法 final HttpHost host,//连接的远端服务器 final InetSocketAddress localAddress, final int connectTimeout,//连接的超时时间,即tcp的超时时间 final SocketConfig socketConfig,//建立socket连接的配置参数,比如socketTimeOUT,是否重用,soLinger,keepAlive,接受缓存和发送缓存大小 final HttpContext context) throws IOException {//httpContext在整个httpConnection的使用过程中使用 final Lookup registry = getSocketFactoryRegistry(context); final ConnectionSocketFactory sf = registry.lookup(host.getSchemeName());//根据schema确定socketFactory的类型,分为http的和https的,获得一个socketFactory,用于产生一个socket 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);//如果host中没有指定port则根据schema确定port,如果是http的就是80,https的是433 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);//产生一个socket 下面都是配置socket的参数 sock.setSoTimeout(socketConfig.getSoTimeout());//包的最大间隔时间 sock.setReuseAddress(socketConfig.isSoReuseAddress());//是否使用time_wait阶段的端口, sock.setTcpNoDelay(socketConfig.isTcpNoDelay()); sock.setKeepAlive(socketConfig.isSoKeepAlive()); final int linger = socketConfig.getSoLinger(); if (linger >= 0) { sock.setSoLinger(true, linger); } conn.bind(sock);//这个httpConnection使用这个socket 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) conn.bind(sock); 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; } } } } }
上面这个类的最终的作用就是创建一个socket然后和服务端进行连接,并将一个socket关联到一个httpConnection上,刚创建的httpConnection的isOpen方法是false,此时就要进行connect。这个部分的代码太深了,在BHttpConnectionBase中,这个类是所有httpConnection的基类,我没仔细看,我认为这个类已经和http协议涉及的太密切了,不再是应用层的东西了,所以就没有看。
这篇博客就是讲了httpConnection是如何使用的,主要就是从pool中借出一个来,然后添加底层的socket tcp连接,然后使用其发送请求,至于详细的发送请求的那一块直接略过了,因为那些是http自己的小细节,已经不属于我们的应用层了,所以就没看。
至此http的存储以及获取还有使用已经都看了,下一篇看一下httpConnection的归还以及关闭。