HttpClient 4.0.1 - 如何释放连接?

74

我有一个循环遍历一堆URL,对于每个URL,我正在执行以下操作:

private String doQuery(String url) {

  HttpGet httpGet = new HttpGet(url);
  setDefaultHeaders(httpGet); // static method
  HttpResponse response = httpClient.execute(httpGet);   // httpClient instantiated in constructor

  int rc = response.getStatusLine().getStatusCode();

  if (rc != 200) {
    // some stuff...
    return;
  }

  HttpEntity entity = response.getEntity();

  if (entity == null) {
    // some stuff...
    return;
  }

  // process the entity, get input stream etc

}
第一次查询没问题,第二次会抛出以下异常:

Exception in thread "main" java.lang.IllegalStateException: Invalid use of SingleClientConnManager: connection still allocated. Make sure to release the connection before allocating another one. at org.apache.http.impl.conn.SingleClientConnManager.getConnection(SingleClientConnManager.java:199) at org.apache.http.impl.conn.SingleClientConnManager$1.getConnection(SingleClientConnManager.java:173)......

这只是一个简单的单线程应用程序。如何释放连接?

12个回答

99

Httpcomponents 4.1推荐的方式是关闭连接并释放任何相关资源:

EntityUtils.consume(HttpEntity)

这里传递的 HttpEntity 是一个响应实体。


这是4.1的正确答案。Richard H的那个对我不起作用。 - Alexandre Martins
1
我遇到了相同的问题。这个解决方案有效,但并没有涵盖所有可能的情况。我猜测httpClient正在被重复使用。如果一些请求失败,正如我们在HTTP中所期望的那样,不清楚如何释放已分配的资源。即使按照Apache提供的多个不同异常处理程序的示例,我仍然遇到了同样的问题 :( - Rafael
@Rafael,如果在获取响应实体的句柄之前出现异常,你可以使用HttpRequestBase.releaseConnection()或HttpUriRequest.abort()。 - Yadu
4
EntityUtils类中没有consume方法。参考链接:http://developer.android.com/reference/org/apache/http/util/EntityUtils.html - jb11
如果出现异常,那么可关闭的响应对象将为null怎么办?如果我不使用try with resources,如何在这种情况下释放连接?因为现在我无法调用response.close()。谢谢 - Jaraws

34

这看起来很有效:

      if( response.getEntity() != null ) {
         response.getEntity().consumeContent();
      }//if

即使您没有打开其内容,也不要忘记消耗该实体。例如,如果您期望从响应中获得HTTP_OK状态但未获得它,则仍必须消耗实体!


13
consumeContent()现在已过时(4.1.2版本)。一句话替代方案:EntityUtils.consume(response.getEntity()); - Gili Nachum
@Snicolas如果我们没有consumeContent()实体会发生什么? - Kasun Siyambalapitiya
不确定,但我会说你泄漏了内存。我有点记得你也会收到错误消息。 - Snicolas
2
对于那些想知道的人,这里有个后续:如果你不消耗实体,HttpClient 将认为请求仍在进行中。一旦这些未被消耗的请求堆积起来,随后的请求将无限期地阻塞。 - Jonathan Fuerth
现在似乎已经被弃用了。 - Paul Cuddihy
如果出现异常,那么可关闭的响应对象将为null怎么办?如果我不使用try with resources,如何在这种情况下释放连接?因为现在我无法调用response.close()。谢谢 - Jaraws

24
回答自己的问题:为了释放连接(以及与请求相关的任何其他资源),您必须关闭HttpEntity返回的InputStream:

回答自己的问题:为了释放连接(以及与请求相关的任何其他资源),您必须关闭HttpEntity返回的InputStream:

InputStream is = entity.getContent();

.... process the input stream ....

is.close();       // releases all resources

文档中得知:


3
我不这么认为,我尝试做这件事,并收到了“Invalid use of SingleClientConnManager: connection still allocated”错误提示。也许可以通过从httpClient获取的ClientConnectionManager来完成,但这似乎不是正确的方法。 - lisak
我也不知道。你是否在重用HTTPClient?当请求失败时,你如何确保它释放资源? - Rafael
2
我可以确认,通过关闭InputStream,我看到调试消息:org.apache.http.impl.conn.BasicClientConnectionManager(18314):释放连接。 - Jona
1
我确认这个可以正常工作。谢谢。(顺便说一句,使用HttpComponents-4.1的"EntityUtils.consume(httpEntity)"也可以完成任务)。 - Hartmut Pfarr
Richard是正确的,调用接收输入流上的“close()”会通知观察者关闭,然后观察者关闭流本身并将连接标记为“可重用”(httpclient版本4.5.1)。 - mulya
关闭内容流和关闭响应的区别在于前者将尝试通过消耗实体内容来保持底层连接的活动状态,而后者会立即关闭并丢弃连接。请参见https://hc.apache.org/httpcomponents-client-ga/tutorial/html/fundamentals.html#d5e145。 - JL_SO

19

13
我会提供一份详细的回答,专门针对Apache HttpClient 4.0.1。我正在使用这个HttpClient版本,因为它由WAS v8.0提供,并且我需要在Apache Wink v1.1.1中使用该提供的HttpClient,该版本也由WAS v8.0提供,以进行一些针对Sharepoint的NTLM身份验证REST调用。
引用Apache HttpClient邮件列表上Oleg Kalnichevski的话:

Pretty much all this code is not necessary. (1) HttpClient will automatically release the underlying connection as long as the entity content is consumed to the end of stream; (2) HttpClient will automatically release the underlying connection on any I/O exception thrown while reading the response content. No special processing is required in such as case.

In fact, this is perfectly sufficient to ensure proper release of resources:

HttpResponse rsp = httpclient.execute(target, req); 
HttpEntity entity = rsp.getEntity(); 
if (entity != null) {
     InputStream instream = entity.getContent();
     try {
         // process content
     } finally {
         instream.close();
         // entity.consumeContent() would also do
     } 
}

That is it.

来源


如果实体为空,会发生什么?那我们不需要关闭连接吗? - cecemel
@cecemel - 嗯,是的……以及你想要如何处理。这取决于情况的范围。 - Chris Harris
1
EntityUtils.consume(entity)足够吗?如果是,那么它是否等同于关闭instream - asgs
@asgs - 我会坚持在 finally 块中使用这两行代码之一。 - Chris Harris
1
@asgs - 这取决于Javadoc是否适用于v4.0.1。我认为EntityUtils成为了在v4.0.1之后的某个版本中更受欢迎的方式。我回答中的邮件列表摘录是来自v4.0.1时期的。 - Chris Harris
显示剩余2条评论

7
如果不需要使用响应,则可以使用以下代码中止请求:
// Low level resources should be released before initiating a new request
HttpEntity entity = response.getEntity();

if (entity != null) {
    // Do not need the rest
    httpPost.abort();
}

参考资料: http://hc.apache.org/httpcomponents-client-ga/tutorial/html/fundamentals.html#d5e143

Apache HttpClient 版本:4.1.3

以上内容介绍了 Apache HttpClient 的基础知识。

这是一个很棒的回应。 - Mr. Polywhirl

4
我在多线程环境(Servlets)中使用HttpClient时,遇到了一个问题。一个Servlet仍然保留连接,而另一个Servlet想要获取连接。
解决方案: 版本4.0使用ThreadSafeClientConnManager 版本4.2使用PoolingClientConnectionManager,并设置以下两个setter:
setDefaultMaxPerRoute
setMaxTotal

3
我正在使用HttpClient 4.5.3,使用CloseableHttpResponse#close对我有用。
    CloseableHttpResponse response = client.execute(request);

    try {
        HttpEntity entity = response.getEntity();
        String body = EntityUtils.toString(entity);
        checkResult(body);
        EntityUtils.consume(entity);
    } finally {
        response.close();
    }

使用Java7及以上版本:

try (CloseableHttpResponse response = client.execute(request)) {
   ...
}

2
从Java 7开始使用: try (CloseableHttpResponse response = client.execute(request)) { ... } <-- 不需要在finally中关闭 - Christophe Roussy
使用CloseableHttpClient#close对我有效。在该代码中,您关闭的是响应而不是客户端-这是一个重大差别。 - JL_SO
1
@JL_SO 关闭响应将释放客户端与连接池管理器之间的连接。客户端可以在以后的某个时间租用另一个连接。如果您关闭了客户端,则需要创建一个新的客户端实例,这在大多数情况下是不必要的。 - Fan Jin
@FanJin 是的,这是正确的,但我的意思是你说“Client#close worked for me”,然后粘贴了代码,而该代码实际上使用了Response#close(而不是client#close)。 - JL_SO
你是对的,我已经更新了我的答案。 - Fan Jin

2

HTTP HEAD请求需要稍微特殊处理,因为response.getEntity()是空的。 相反,您必须捕获传递给HttpClient.execute()的HttpContext并检索连接参数以关闭它(在HttpComponents 4.1.X中)。

HttpRequest httpRqst = new HttpHead( uri );
HttpContext httpContext = httpFactory.createContext();
HttpResponse httpResp = httpClient.execute( httpRqst, httpContext );

...

// Close when finished
HttpEntity entity = httpResp.getEntity();
if( null != entity )
  // Handles standard 'GET' case
  EntityUtils.consume( entity );
else {
  ConnectionReleaseTrigger  conn =
      (ConnectionReleaseTrigger) httpContext.getAttribute( ExecutionContext.HTTP_CONNECTION );
  // Handles 'HEAD' where entity is not returned
  if( null != conn )
    conn.releaseConnection();
}

HttpComponents 4.2.X新增了一个releaseConnection()方法在HttpRequestBase中,可以使这个过程更加简单。


1
如果您想重复使用连接,则必须在每次使用后完全消耗内容流,如下所示:
EntityUtils.consume(response.getEntity())

注意:即使状态码不是200,您也需要消耗内容流。不这样做会在下次使用时引发以下异常:
Exception in thread "main" java.lang.IllegalStateException: Invalid use of SingleClientConnManager: connection still allocated. Make sure to release the connection before allocating another one.
如果只使用一次,则简单关闭连接将释放与其关联的所有资源。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接