上一篇中主要说了借出后怎么使用,这一篇主要介绍使用完了如何归还,再次回到使用的部分的代码里面,MinimalClientExec.execute(HttpRoute, HttpRequestWrapper, HttpClientContext, HttpExecutionAware)中的最后几行
final ConnectionHolder releaseTrigger = new ConnectionHolder(log, connManager, managedConn);//用一个对象封装借出来的connection,方便复用 。。。省略很多不需要的 if (reuseStrategy.keepAlive(response, context)) {//如果是keepalive的则继续使用 // Set the idle duration of this connection final long duration = keepAliveStrategy.getKeepAliveDuration(response, context); releaseTrigger.setValidFor(duration, TimeUnit.MILLISECONDS);//设置校验前的截止时间 releaseTrigger.markReusable();//标记为可以重复使用的 } else { releaseTrigger.markNonReusable();//如果不是1.1的keepalive,则标记为不可复用的。 } // check for entity, release connection if possible final HttpEntity entity = response.getEntity(); if (entity == null || !entity.isStreaming()) {//不管这种没有stream的,我们使用tomcat这种都是有stream的,我猜测这种是为了测试用的 releaseTrigger.releaseConnection(); return new HttpResponseProxy(response, null); } else { return new HttpResponseProxy(response, releaseTrigger);//最后返回的一个response对象是一个代理,他里面封装了真正的response和releaseTrigger,也就自动包含了httpconnection }
看下下httpResponseProxy的代码
private final HttpResponse original;//包装的原始的httpResponse, private final ConnectionHolder connHolder;//里面含有httpConnection,用于重用这个httpConnection。 public HttpResponseProxy(final HttpResponse original, final ConnectionHolder connHolder) { this.original = original; this.connHolder = connHolder; ResponseEntityProxy.enchance(original, connHolder);//将包装的原始的HTTPResponse进行加强,这个方法很重要 }
上面的HttpResponseProxy仅仅是一个代理,我们在使用reponse的时候使用的是getEntity方法,使用的是getStatusLine方法,他都是调用的里面封装的original的方法,但是在调用getEntity.getContent的时候,也就是获得相应的具体的内容的时候就不是,因为上面的构造方法里面加强了original返回的Entity对象,看一下ResponseEntityProxy.enchance(HttpResponse, ConnectionHolder):
public static void enchance(final HttpResponse response, final ConnectionHolder connHolder) { final HttpEntity entity = response.getEntity();//这里的reponse就是上面的original,也就是最原始的结果 if (entity != null && entity.isStreaming() && connHolder != null) { response.setEntity(new ResponseEntityProxy(entity, connHolder));//加强最原始的结果里面的entity,看下这个类ResponseEntityProxy,他也是一个Entity,重点看下他的getContent方法 } } @Override public InputStream getContent() throws IOException { return new EofSensorInputStream(this.wrappedEntity.getContent(), this);//返回的是一个对象,封装了最原始的inputStream,还有就是把自己传进去了,看一下这个EofSensorInputStream }
他的java注释上写着:他用于在close的时候触发操作,主要用于释放底层连接,他的构造方法里面有一个EofSensorWatcher,也就是监视器模式的应用,在eof(end of file)的时候会调用监视器。
这样当调用response.getEntity.getContent的时候就会返回一个EofSensorInputStream,看一下他的read方法:
@Override public int read() throws IOException { int l = -1; if (isReadAllowed()) { try { l = wrappedStream.read(); checkEOF(l); } catch (final IOException ex) { checkAbort(); throw ex; } } return l; } @Override public int read(final byte[] b, final int off, final int len) throws IOException { int l = -1; if (isReadAllowed()) { try { l = wrappedStream.read(b, off, len); checkEOF(l); } catch (final IOException ex) { checkAbort();throw ex; } } return l; } @Override public int read(final byte[] b) throws IOException { return read(b, 0, b.length); }
可以发现他的read操作都是调用的封装的inputStream的方法,没有任何的加强,再看看他的close方法:
public void close() throws IOException { selfClosed = true;//表示当前的已经关闭了, checkClose();//通知监视器,调用他的相关方法 } protected void checkClose() throws IOException { if (wrappedStream != null) { try { boolean scws = true; // should close wrapped stream? if (eofWatcher != null) { scws = eofWatcher.streamClosed(wrappedStream);//调用关闭方法 } if (scws) { wrappedStream.close(); } } finally { wrappedStream = null; } } }
之前穿进去的监视器就是ResponseEntityProxy,看看他的streamClosed方法:
public boolean streamClosed(final InputStream wrapped) throws IOException { try { final boolean open = connHolder != null && !connHolder.isReleased();//这里就是包装了httpConnection的对象,用于复用 try { wrapped.close(); releaseConnection();//这里可以发现在调用完了只有就会地哦啊用releaseConnection方法, } catch (final SocketException ex) { if (open) {throw ex;} } } finally {cleanup();} return false; } public void releaseConnection() throws IOException { if (this.connHolder != null) { try { if (this.connHolder.isReusable()) {//如果能复用,则调用release方法,这里的能复用就是一开始的是否是keepalive的判断,如果是则可以复用, this.connHolder.releaseConnection();//下面有这个方法: } } finally { cleanup(); } } }
ConnectionHolder.releaseConnection():
public void releaseConnection() { synchronized (this.managedConn) { if (this.released) { return; } this.released = true; if (this.reusable) {//如果是1.1的就可以复用 this.manager.releaseConnection(this.managedConn,this.state, this.validDuration, this.tunit);//manager即之前穿进去的PoolingHttpClientConnectionManager,复用的话就要复用这个connection,还要更新他的校验时间 } else {//如果不是,则关闭整个http连接管理器 try { this.managedConn.close(); } catch (final IOException ex) { if (this.log.isDebugEnabled()) { this.log.debug(ex.getMessage(), ex); } } finally { this.manager.releaseConnection(this.managedConn, null, 0, TimeUnit.MILLISECONDS); } } } }
上面经过了好几层代理,最开始是HttpResponseProxy代理了最底层的httpResponse,在代理的时候对最底层的HTTPResponse的entity进行了增强,增强后的entity的getContent方法返回的是一个可以监事关闭操作的inpustream,在关闭的时候会调用connectionHolder的释放连接的操作,释放连接就会放到缓存池,顺便更新校验时间。所以,现在只要关闭了最外层的inpustream,就会自动的释放连接,放回缓存池。还有PoolingHttpClientConnectionManager的释放连接方法没看:
public void releaseConnection(final HttpClientConnection managedConn,final Object state,final long keepalive, final TimeUnit tunit) {//第一个参数及要释放的connection,第三个是剩余的keepalive时间,第四个是时间单位 synchronized (managedConn) { final CPoolEntry entry = CPoolProxy.detach(managedConn);//最早借出来的是CPoolEntry,然后再返回的时候是使用CPoolProxy代理了,现在是逆向操作。 if (entry == null) { return; } final ManagedHttpClientConnection conn = entry.getConnection();//里面的connection try { if (conn.isOpen()) { final TimeUnit effectiveUnit = tunit != null ? tunit : TimeUnit.MILLISECONDS; entry.setState(state); entry.updateExpiry(keepalive, effectiveUnit);//更新失效时间 } } finally { this.pool.release(entry, conn.isOpen() && entry.isRouteComplete());//继续调用CPool的release方法,将连接放回缓存池。 } } }
CPool的释放连接的方法:
@Override public void release(final E entry, final boolean reusable) { this.lock.lock(); try { if (this.leased.remove(entry)) {//从借出的结合中删除 final RouteSpecificPool<T, C, E> pool = getPool(entry.getRoute());//找到对应的池子 pool.free(entry, reusable);//添加进去 if (reusable && !this.isShutDown) {//可以复用,则添加到可以复用的集合里面 this.available.addFirst(entry); onRelease(entry);//回调函数,现在没有内容 } else { entry.close(); } PoolEntryFuture<E> future = pool.nextPending();//这个是唤醒正在等待的线程,现在还回来了,可以继续尝试获取了。 if (future != null) { this.pending.remove(future); } else { future = this.pending.poll(); } if (future != null) { future.wakeup(); } } } finally { this.lock.unlock(); } }
这样,整个归还的流程就完了。 可以发现,在使用httpClient的时候只要调用最后的inputStream的close方法,借出去的连接会自动的还回到缓存池中,也就是使用EntityUtils.consume(entiry)就可以了。
上面看完了归还,还引发了另一个问题,就是尽管我们关闭了inpustream,但是其实socket底层并没有关闭,仅仅是将含有socket的connection换回到了连接池,还有就是那些过期的连接也是一直在连接池里面,如何关掉socket、关掉那些过期的socket呢?
对于在关闭程序时关闭所有的连接的需求,可以将整个httpClient作为spring的一个bean使用,然后再关闭容器的时候关闭httpClient,这样整个缓存池就会全部被关闭,对于那些在程序运行期间关闭那些过期的连接的需求(过期的连接在这些连接被获取的时候也是会检查的,但是在没有被检查到的时刻之前还是会造成资源浪费),可以打开一个配置,让后台自动检查过期的或者是长时间没有使用的连接(过期的连接则是超过了keepalive的时间,而长时间没有使用的是最后一次更新时间太久的连接),具体操作是:
HttpClientBuilder hcb = HttpClientBuilder.create(); hcb.setConnectionManager(poolManager);//忽略 hcb.setDefaultRequestConfig(httpConfig);//忽略 hcb.evictExpiredConnections();//设置淘汰过期的连接 hcb.evictIdleConnections(maxIdleTime, maxIdleTimeUnit)//设置关闭空闲的连接,后面的参数制定了最大的空闲时间,开启这两个之后,就会启动一个定时任务,定时任务每隔maxIdleTime运行一次,调用AbstractConnPool.closeIdle(long, TimeUnit)和closeExpired方法。
好了,httpClient的源码就看到这里了,对于我们应用层的应用已经够深了。在整个过程中我一直没有写和http连接直接相关的操作,比如ConnectionFactory、ConnectionConfig、HttpRequestExecutor,因为这些都是http方面的操作,对于我们来说是透明的,并不妨碍我们使用,不过看看还是好的,比如我看了HttpRequestExecutor的HttpRequestExecutor.execute(HttpRequest, HttpClientConnection, HttpContext)方法,就看到了http状态码的使用,在100的时候会继续发送socket请求。
博客中应该是有很多的错误,如果有网友看到哪里错了,请联系我,我的qq是1308567317,我好改正过来,一面误导大家。
相关推荐
它包含了HTTP连接的建立、保持和关闭,以及HTTP方法(GET, POST等)的实现。HTTP Core还支持传输编码(如chunked编码),内容编码(如gzip压缩)和HTTP连接的复用,这极大地提高了网络通信的效率。 **httpclient-...
赠送jar包:httpclient-4.5.6.jar; 赠送原API文档:httpclient-4.5.6-javadoc.jar; 赠送源代码:httpclient-4.5.6-sources.jar; 赠送Maven依赖信息文件:httpclient-4.5.6.pom; 包含翻译后的API文档:httpclient...
赠送jar包:httpclient-4.5.13.jar; 赠送原API文档:httpclient-4.5.13-javadoc.jar; 赠送源代码:httpclient-4.5.13-sources.jar; 赠送Maven依赖信息文件:httpclient-4.5.13.pom; 包含翻译后的API文档:...
httpclient-4.1-alpha1.jar httpclient-4.1-alpha1.jar httpclient-4.1-alpha1.jar httpclient-4.1-alpha1.jar httpclient-4.1-alpha1.jar
赠送jar包:httpclient-4.4.1.jar; 赠送原API文档:httpclient-4.4.1-javadoc.jar; 赠送源代码:httpclient-4.4.1-sources.jar; 赠送Maven依赖信息文件:httpclient-4.4.1.pom; 包含翻译后的API文档:httpclient...
赠送jar包:httpclient-4.5.6.jar; 赠送原API文档:httpclient-4.5.6-javadoc.jar; 赠送源代码:httpclient-4.5.6-sources.jar; 赠送Maven依赖信息文件:httpclient-4.5.6.pom; 包含翻译后的API文档:httpclient...
赠送jar包:httpclient-4.5.13.jar; 赠送原API文档:httpclient-4.5.13-javadoc.jar; 赠送源代码:httpclient-4.5.13-sources.jar; 赠送Maven依赖信息文件:httpclient-4.5.13.pom; 包含翻译后的API文档:...
赠送jar包:httpclient-4.5.12.jar; 赠送原API文档:httpclient-4.5.12-javadoc.jar; 赠送源代码:httpclient-4.5.12-sources.jar; 赠送Maven依赖信息文件:httpclient-4.5.12.pom; 包含翻译后的API文档:...
用于http请求的jar包
赠送jar包:httpclient-4.5.5.jar; 赠送原API文档:httpclient-4.5.5-javadoc.jar; 赠送源代码:httpclient-4.5.5-sources.jar; 包含翻译后的API文档:httpclient-4.5.5-javadoc-API文档-中文(简体)版.zip ...
httpclient常用的jar包,便于大家使用
赠送jar包:httpclient-4.5.10.jar; 赠送原API文档:httpclient-4.5.10-javadoc.jar; 赠送源代码:httpclient-4.5.10-sources.jar; 赠送Maven依赖信息文件:httpclient-4.5.10.pom; 包含翻译后的API文档:...
赠送jar包:httpclient-4.5.2.jar; 赠送原API文档:httpclient-4.5.2-javadoc.jar; 赠送源代码:httpclient-4.5.2-sources.jar; 包含翻译后的API文档:httpclient-4.5.2-javadoc-API文档-中文(简体)-英语-对照...
httpclient-4.5所需jar包,里面包含httpclient-4.5.jar等等10个必须的开发包。 1.commons-codec-1.9.jar 2.commons-logging-1.2.jar 3.fluent-hc-4.5.jar 4.httpclient-4.5.jar 5.httpclient-cache-4.5.jar 6....
Http协议使用封装jar包(commons-codec-1.3.jar、commons-httpclient-3.1.jar、commons-logging-1.1.jar),资源包括: commons-codec-1.11.jar; commons-logging-1.2.jar; fluent-hc-4.5.8.jar; httpclient-...
赠送jar包:httpclient-4.4.jar; 赠送原API文档:httpclient-4.4-javadoc.jar; 赠送源代码:httpclient-4.4-sources.jar; 赠送Maven依赖信息文件:httpclient-4.4.pom; 包含翻译后的API文档:httpclient-4.4-...
httpclient-4.5.13-sources.jar
赠送jar包:httpclient-4.2.5.jar; 赠送原API文档:httpclient-4.2.5-javadoc.jar; 赠送源代码:httpclient-4.2.5-sources.jar; 赠送Maven依赖信息文件:httpclient-4.2.5.pom; 包含翻译后的API文档:httpclient...
httpclient-4.3-beta1
在项目中集成HttpClient,通常需要将`commons-httpclient-3.1-rc1.jar`添加到项目的类路径中,然后通过创建HttpClient实例,设置必要的配置,并调用相关方法发起HTTP请求。 接下来是`commons-logging.jar`,它是...