HttpClient解读(2)-http连接的使用

阅读更多

上一个文章说了http连接的缓存池,以及借出的过程,借出使用的是HttoRoute,先根据route定位到RouteSpecificPool,然后从RouteSpecificPool中借出一个httpConnection,借出后都是用一个Entry包装的,entry里面含有httpRouter+httpConnection。这一篇说一下借出之前的过程以及借出后的使用操作。

缓存的对象是CPool,他的使用是在HttpClientConnectionManager里面,这个类从他的名字就能看出来是管理HttpConnection的,他的实现类一般都是用的PoolingHttpClientConnectionManager,即使用缓存的httpConnection管理器,这个类的里面就会用到了CPool类,在创建一个PoolingHttpClientConnectionManager的时候可以发现:

public PoolingHttpClientConnectionManager(
	final HttpClientConnectionOperator httpClientConnectionOperator,
	final HttpConnectionFactory connFactory,
	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 Future future = 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 Lookup socketFactoryRegistry;
	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的归还以及关闭。

 

你可能感兴趣的:(HttpClient解读(2)-http连接的使用)