Исключение с использованием HttpRequest.execute(): недопустимое использование SingleClientConnManager: соединение все еще выделено

Я использую google-api-client-java 1.2.1-alpha для выполнения POST-запроса и получаю следующую трассировку стека при выполнении() HttpRequest.

Это происходит сразу после того, как я поймаю и проигнорирую ошибку 403 из предыдущего POST на тот же URL-адрес и повторно использую транспорт для последующего запроса. (Это циклическая вставка нескольких записей в один и тот же канал 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)

Почему приведенный ниже код пытается установить новое соединение?


person David Bullock    schedule 06.01.2011    source источник
comment
Похоже, это все еще проблема с версией 1.11.0-beta :/   -  person sjngm    schedule 13.09.2012
comment
Для всех, кто прибывает сюда после попытки использовать ответы и все еще получает предупреждения - я нашел правильный ответ здесь: tech.chitgoks.com/2011/05/05/   -  person Steelight    schedule 08.11.2012
comment
@Steelight — с помощью tech.chitgoks.com решил мою проблему.   -  person Cale Sweeney    schedule 25.10.2017


Ответы (9)


Вам необходимо использовать тело ответа, прежде чем вы сможете повторно использовать соединение для другого запроса. Вы должны не только прочитать статус ответа, но и прочитать ответ InputStream полностью до последнего байта, при этом вы просто игнорируете прочитанные байты.

person BalusC    schedule 07.01.2011
comment
Вот оно! В случае с google-api-java-client это означало перехват IOException, брошенного HttpResponse.execute(), тестирование/приведение его к HttpResponseException, доступ к члену response, а затем вызов paraseAsString() на нем. (Что в любом случае оказалось полезной информацией :-) - person David Bullock; 07.01.2011
comment
Существует также метод HttpEntity.consumeContent() для удаления содержимого. - person Grzegorz Adam Hankiewicz; 13.04.2012
comment
EntityUtils.consume(entity) — потреблениеContent теперь устарело. - person David Carboni; 25.10.2012
comment
Просто отметим, что в последних версиях Google Http Java Client (по крайней мере, 1.16, но, возможно, и раньше) вызов HttpRequest.execute() автоматически очищает эти ресурсы, если метод не возвращает объект HttpResponse. Кроме того, JavaDoc HttpResponse теперь рекомендует вызывать response.disconnect() в случае неполного чтения содержимого HTTP-ответа (в блоке finally). - person David Bullock; 23.12.2013
comment
Та же проблема с RestEasy 3.0.4, который внутренне использует BasicClientConnectionManager из apache-httpclient 4.2.1. Спасибо @BalusC. Кроме того, я никогда ничего не читал о полном чтении потока, куда следует обратиться, чтобы узнать об этих ловушках? (с точки зрения документации, кроме исходного кода) - person alegria; 04.12.2019
comment
@dashboard: я также нигде не видел, чтобы это было задокументировано. Думаю, для этого и предназначен Stack Overflow. - person BalusC; 04.12.2019

Я столкнулся с аналогичной проблемой при использовании HttpClient с Jetty для создания тестовой среды. Мне пришлось создать несколько запросов к Servlet от моего клиента, но при выполнении он выдавал одно и то же исключение.

Я нашел альтернативу на 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;
}
person Ujjwal Wadhawan    schedule 18.07.2011
comment
Спасибо, сработало неплохо. Однако мне любопытна необходимость создания двух DefaultHttpClients - person htafoya; 26.06.2012
comment
Он использует HttpParams по умолчанию (получает их от клиента) вместо создания собственного с нуля. - person Marcin Gil; 27.06.2012
comment
Это решило мою проблему, но можно ли использовать это в приложении для Android. - person manish; 17.03.2014
comment
Интересно, что это частично работает для меня, в конечном итоге блокируя выполнение оставшегося кода, когда я делаю несколько HTTP-запросов в цикле и не использую ответы. И никаких сбоев, я не стал слишком долго ждать, чтобы увидеть, возникает ли ошибка тайм-аута. Поэтому вместо этого я пошел по пути выделения новых DefaultHttpClients для каждого запроса, и это сработало для меня, так как я не запускаю цикл очень долго. - person David; 16.02.2017

Аналогичное сообщение об исключении (по крайней мере, начиная с HTTP-клиента Apache Jarkata Commons 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.2 DefaultHttpClient потокобезопасным (потокобезопасным в том смысле, что два или более потока могут взаимодействовать с ним, не получая сообщения об ошибке выше)? Предоставьте DefaultHttpClient пул соединений ClientConnectionManager в виде org.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);
        }
    }
}
person Abdull    schedule 07.02.2013

Это часто задаваемый вопрос. Ответ BalusC правильный. Уловите HttpReponseException и вызовите HttpResponseException.ответ.игнорировать(). Если вам нужно прочитать сообщение об ошибке, используйте response.parseAsString(), если вы не знаете тип содержимого ответа, иначе, если вы знаете тип содержимого, используйте response.parseAs(MyType.class).

Простой фрагмент кода из YouTubeSample.java в youtube-jsonc-sample (хотя обычно вы хотите сделать что-то умнее в реальном приложении):

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

Полное раскрытие: я являюсь владельцем google-api-java-client проект.

person Yaniv Inbar    schedule 10.01.2011
comment
Разве тот факт, что это часто задаваемый вопрос, не говорит о том, что в дизайне вашей библиотеки есть проблема с удобством использования? - person sanity; 08.06.2011
comment
@sanity Или, может быть, в самом стеке http;) - person krosenvold; 16.06.2011

У меня была такая же проблема с объектом Response jax-rs (resteasy) в моих модульных тестах. Я решил эту проблему вызовом response.releaseConnection();. Метод releaseConnection() применим только к неактивному объекту ClientResponse, поэтому мне пришлось добавить приведение от Response к ClientResponse.

person markus    schedule 18.10.2012
comment
Это спасло мой день! В моем случае мне пришлось перейти к org.jboss.resteasy.client.jaxrs.internal.ClientResponse, чтобы еще больше сузить его. - person user2081279; 22.03.2019

Попробуй это

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");
    }
person Silambarasan Poonguti    schedule 10.06.2014

Хорошо, у меня похожая проблема, все эти решения не работают, я тестировал на каком-то устройстве, проблема была в дате в устройстве, это был 2011 год вместо 2013 года, проверьте также, это может помочь.

person marko    schedule 08.02.2013

Прочитайте 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() + "" );
    }
}
person lucasddaniel    schedule 24.08.2014

просто используйте ответ, как показано ниже, это решит проблему

response.getEntity().consumeContent();
person Mohammed Rafeeq    schedule 24.06.2019