使用HttpRequest.execute()的异常:无效使用SingleClientConnManager:仍分配了连接


81

我正在使用google-api-client-java 1.2.1-alpha执行POST请求,并在我执行execute()HttpRequest时得到以下堆栈跟踪信息。

在我捕获并忽略从先前POST到相同URL的403错误并重新用于后续请求的传输之后,它立即发生。(这是将多个条目插入同一ATOM提要的循环)。

403之后,我应该做些什么来“清理”吗?

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)
    at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:390)
    at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:641)
    at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:576)
    at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:554)
    at com.google.api.client.apache.ApacheHttpRequest.execute(ApacheHttpRequest.java:47)
    at com.google.api.client.http.HttpRequest.execute(HttpRequest.java:207)
    at au.com.machaira.pss.gape.RedirectHandler.execute(RedirectHandler.java:38)
    at au.com.machaira.pss.gape.ss.model.records.TableEntry.executeModification(TableEntry.java:81)

为什么下面的代码会尝试获取新的连接?


这似乎仍然是版本1.11.0-beta的问题:/
sjngm 2012年

5
为了让任何人在尝试使用响应后仍然收到警告后到达这里,我在这里找到了正确的答案:tech.chitgoks.com/2011/05/05/…– Steelight
2012年

@Steelight-使用tech.chitgoks.com方法解决了我的问题。
Cale Sweeney

Answers:


82

您需要先使用响应主体,然后才能将连接重用于另一个请求。您不仅应该读取响应状态,还应该InputStream完整读取响应到最后一个字节,从而忽略所读取的字节。


1
就是这样!在的情况下google-api-java-client,这意味着捕获由IOException抛出的异常HttpResponse.execute(),将其测试/投射到HttpResponseException,访问该response成员,然后对其进行调用paraseAsString()。(无论如何,这都是有用的信息:-)
David Bullock

5
还有一个HttpEntity.consumeContent()方法来丢弃内容。
Grzegorz亚当Hankiewicz 2012年

3
EntityUtils.consume(entity)-现在不赞成使用contentContent。
大卫·卡波尼

仅需注意,从最新版本的Google Http Java Client(至少为1.16,但可能更早)开始,HttpRequest.execute()如果该方法无法返回HttpResponse对象,则调用将自动清除这些资源。此外,HttpResponse现在的JavaDoc建议response.disconnect()在未完全读取HTTP响应的内容的情况下(以finally块为单位)进行调用。
大卫·布洛克

RestEasy 3.0.4的相同问题,内部使用apache-httpclient 4.2.1的BasicClientConnectionManager。谢谢@BalusC。另外,我从没有读过任何有关完全阅读流的内容,当意识到这些陷阱时应该去哪里看?(从文档角度来说,不是源代码)
仪表板

42

当将HttpClient与Jetty一起使用时,我遇到了类似的问题。我必须从客户端向Servelet创建多个请求,但是在执行时却给出了相同的异常。

我在http://foo.jasonhudgins.com/2010/03/http-connections-revisited.html找到了替代方法

您还可以使用以下方法实例化客户端。

public static DefaultHttpClient getThreadSafeClient()  {

    DefaultHttpClient client = new DefaultHttpClient();
    ClientConnectionManager mgr = client.getConnectionManager();
    HttpParams params = client.getParams();
    client = new DefaultHttpClient(new ThreadSafeClientConnManager(params, 

            mgr.getSchemeRegistry()), params);
    return client;
}

谢谢,做得很好。但是我很好奇是否有必要创建两个DefaultHttpClients
htafoya 2012年

2
他使用默认的HttpParams(从客户端获取它们),而不是从头开始创建自己的HttpParams。
Marcin Gil 2012年

那解决了我的问题,但是可以在android应用程序中使用它。
manish

有趣的是,这对我来说部分起作用,当我在一个循环中发出多个HTTP请求并且不占用响应时,最终阻止了其余代码的执行。而且也没有失败,我没有等太久就看看是否发生超时错误。因此,我改为为每个请求分配新的DefaultHttpClients,这对我有用,因为我运行循环的时间不长。
大卫,

9

类似的异常消息(至少从Apache Jarkata Commons HTTP Client 4.2开始)是:

java.lang.IllegalStateException: Invalid use of BasicClientConnManager: connection still allocated. Make sure to release the connection before allocating another one.

当两个或多个线程与单个线程交互时,可能会发生此异常org.apache.http.impl.client.DefaultHttpClient

如何使4.2DefaultHttpClient实例具有线程安全性(从两个或多个线程可以与之交互的意义上讲,线程安全性而不会出现以上错误消息)?提供形式为DefaultHttpClient的连接池!ClientConnectionManagerorg.apache.http.impl.conn.PoolingClientConnectionManager

/* using
    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
        <version>4.2.2</version>
    </dependency>
*/

import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.PoolingClientConnectionManager;
import org.apache.http.impl.conn.SchemeRegistryFactory;
import org.apache.http.params.HttpParams;
import org.apache.http.client.methods.HttpGet;

public class MyComponent {

    private HttpClient client;

    {
        PoolingClientConnectionManager conMan = new PoolingClientConnectionManager( SchemeRegistryFactory.createDefault() );
        conMan.setMaxTotal(200);
        conMan.setDefaultMaxPerRoute(200);

        client = new DefaultHttpClient(conMan);

        //The following parameter configurations are not
        //neccessary for this example, but they show how
        //to further tweak the HttpClient
        HttpParams params = client.getParams();
        HttpConnectionParams.setConnectionTimeout(params, 20000);
        HttpConnectionParams.setSoTimeout(params, 15000);
    }


    //This method can be called concurrently by several threads
    private InputStream getResource(String uri) {
        try {
            HttpGet method = new HttpGet(uri);
            HttpResponse httpResponse = client.execute(method);
            int statusCode = httpResponse.getStatusLine().getStatusCode();
            InputStream is = null;
            if (HttpStatus.SC_OK == statusCode) {
                logger.debug("200 OK Amazon request");
                is = httpResponse.getEntity().getContent();
            } else {
                logger.debug("Something went wrong, statusCode is {}",
                        statusCode);
                 EntityUtils.consume(httpResponse.getEntity());
            }
            return is;
        } catch (Exception e) {
            logger.error("Something went terribly wrong", e);
            throw new RuntimeException(e);
        }
    }
}

8

这是一个经常提出的问题。BalusC的回答是正确的。请捕获HttpReponseException,然后调用HttpResponseException。反应忽略()。如果您需要阅读错误消息,请使用响应。如果您不知道响应的内容类型,则使用parseAsString();否则,如果您知道该内容的类型,请使用response。parseAs(MyType.class)。

youtube-jsonc-sample中的YouTubeSample.java中的一个简单代码段(尽管通常情况下,您希望在实际应用程序中做一些更聪明的事情):

  } catch (HttpResponseException e) {
    System.err.println(e.response.parseAsString());
  }

全面披露:我是google-api-java-client项目的所有者。


15
这是一个经常问到的问题,这是否表明您的库设计中存在可用性问题?
神智

@sanity或也许在http堆栈本身中;)
krosenvold

3

Response在单元测试中,jax-rs(resteasy)对象存在相同的问题。我通过对response.releaseConnection(); releaseConnection()-Method的调用仅在resteasyClientResponse对象上解决了这个问题,因此我不得不添加一个从Response到的转换ClientResponse


那挽救了我的一天!就我而言,我不得不转换为org.jboss.resteasy.client.jaxrs.internal.ClientResponse以进一步缩小范围。
user2081279

1

试试这个

HttpResponse response = Client.execute(httpGet);
response.getEntity().consumeContent();
StatusLine statusLine = response.getStatusLine();
int statusCode = statusLine.getStatusCode();
if (statusCode == 200) {
        //task
    Log.i("Connection", "OK");
    }else{
     Log.i("Connection", "Down");
    }

0

好的,我有类似的问题,所有这些解决方案均不起作用,我在某台设备上进行了测试,问题出在设备上,日期是2011年,而2013年,检查这也有帮助。


0

像这样读取InputStream:

if( response.getStatusLine().getStatusCode() == 200 ) {
    HttpEntity entity = response.getEntity();
    InputStream content = entity.getContent();
    try {
        sb = new StringBuilder();
        BufferedReader bufferedReader = new BufferedReader( new InputStreamReader( content ), 8 );
        String line;
        while( ( line = bufferedReader.readLine() ) != null ) {
            sb.append( line );
        }
        bufferedReader.close();
        content.close();
    } catch( Exception ex ) {
        Log.e( "statusCode", ex.getMessage() + "" );
    }
}

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.