Wyjątek przy użyciu HttpRequest.execute (): nieprawidłowe użycie SingleClientConnManager: połączenie nadal przydzielone


81

Używam google-api-client-java 1.2.1-alpha do wykonania żądania POST i otrzymuję następujący ślad stosu, gdy wykonuję () HttpRequest.

Dzieje się tak natychmiast po tym, jak wychwycę i zignoruję błąd 403 z poprzedniego POST pod tym samym adresem URL i ponownie wykorzystam transport dla kolejnego żądania. (Jest w pętli wstawiającej wiele wpisów do tego samego źródła ATOM).

Czy jest coś, co powinienem zrobić, aby „posprzątać” po 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)

Dlaczego poniższy kod miałby próbować uzyskać nowe połączenie?


Wydaje się, że nadal jest to problem z wersją 1.11.0-beta: /
sjngm

5
Dla dobra każdego, kto przyjechał tutaj po próbie skonsumowania odpowiedzi i nadal otrzymuję ostrzeżenia - znalazłem poprawną odpowiedź tutaj: tech.chitgoks.com/2011/05/05/ ...
Steelight

@Steelight - użycie podejścia tech.chitgoks.com rozwiązało mój problem.
Cale Sweeney,

Odpowiedzi:


82

Musisz wykorzystać treść odpowiedzi, zanim będzie można ponownie użyć połączenia dla innego żądania. Powinieneś nie tylko czytać status odpowiedzi, ale w InputStreampełni czytać odpowiedź do ostatniego bajtu, przy czym po prostu ignorujesz odczytane bajty.


1
To było to! W przypadku google-api-java-client, oznaczało to złapanie IOExceptionrzuconego przez HttpResponse.execute(), przetestowanie / rzucenie go HttpResponseException, uzyskanie dostępu do responseczłonka, a następnie wywołanie paraseAsString()go. (Co i tak okazało się użyteczną informacją: -)
David Bullock

5
Istnieje również metoda HttpEntity.consumeContent (), która usuwa zawartość.
Grzegorz Adam Hankiewicz

3
EntityUtils.consume (entity) - consumeContent jest teraz przestarzałe.
David Carboni,

Wystarczy zauważyć, że od najnowszych wersji klienta Google Http Java (co najmniej 1.16, ale prawdopodobnie wcześniej), wywołanie HttpRequest.execute()automatycznie wyczyści te zasoby, jeśli metoda nie zwróci HttpResponseobiektu. Co więcej, JavaDoc od HttpResponseteraz zaleca wywołanie response.disconnect()w przypadku niepełnego odczytania treści odpowiedzi HTTP (w finallybloku).
David Bullock,

Ten sam problem z RestEasy 3.0.4, który wewnętrznie używa BasicClientConnectionManager z apache-httpclient 4.2.1. Dziękuję @BalusC. Nigdy też nie czytałem nic o czytaniu strumienia w pełni, na co należy zwrócić uwagę, aby zdać sobie sprawę z tych pułapek? (z punktu widzenia dokumentacji, inny niż kod źródłowy)
alegria

42

Miałem podobny problem podczas używania HttpClient z Jetty do tworzenia struktury testowej. Musiałem utworzyć wiele żądań do Servelet od mojego klienta, ale dawał ten sam wyjątek podczas wykonywania.

Znalazłem alternatywę pod adresem http://foo.jasonhudgins.com/2010/03/http-connections-revisited.html

Możesz również użyć poniższej metody, aby utworzyć wystąpienie klienta.

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;
}

Dzięki, działało całkiem nieźle.
Ciekawi

2
Używa domyślnych HttpParams (pobiera je od klienta) zamiast tworzyć własne od podstaw.
Marcin Gil

To rozwiązało mój problem, ale czy mogę używać tego w aplikacji na Androida.
manisz

Co ciekawe, to częściowo działa dla mnie, kończy się blokowaniem wykonywania pozostałego kodu, gdy wykonuję kilka żądań HTTP w pętli i nie konsumuję odpowiedzi. I bez awarii, nie czekałem zbyt długo, aby sprawdzić, czy wystąpi błąd przekroczenia limitu czasu. Więc zamiast tego poszedłem drogą przydzielania nowych DefaultHttpClients dla każdego żądania i to zadziałało, ponieważ nie wykonuję pętli zbyt długo.
David

9

Podobny komunikat o wyjątku (od przynajmniej Apache Jarkata Commons HTTP Client 4.2) to:

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

Ten wyjątek może wystąpić, gdy dwa lub więcej wątków współdziała z pojedynczym org.apache.http.impl.client.DefaultHttpClient.

Jak utworzyć DefaultHttpClientochronę wątków w instancji 4.2 ( Threadafe w tym sensie, że dwa lub więcej wątków mogą z nią współdziałać bez wyświetlania powyższego komunikatu o błędzie)? Zapewnij pulę DefaultHttpClientpołączeń ClientConnectionManagerw postaci 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);
        }
    }
}

8

To jest często zadawane pytanie. Odpowiedź BalusC jest poprawna. Proszę złapać HttpReponseException i wywołać HttpResponseException. odpowiedź . ignoruj (). Jeśli chcesz przeczytać komunikat o błędzie, użyj odpowiedzi. parseAsString (), jeśli nie znasz typu zawartości odpowiedzi, w przeciwnym razie, jeśli znasz typ zawartości, użyj odpowiedzi. parseAs (MyType.class).

Prosty fragment kodu z YouTubeSample.java w youtube-jsonc-sample (choć zazwyczaj będziesz chciał zrobić coś mądrzejszego w prawdziwej aplikacji):

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

Pełne ujawnienie: jestem właścicielem projektu google-api-java-client .


15
Czy fakt, że jest to często zadawane pytanie, nie sugeruje, że w projekcie biblioteki występuje problem z użytecznością?
zdrowie psychiczne

@sanity A może w samym stosie http;)
krosenvold

3

Miałem ten sam problem z obiektem jax-rs (resteasy) Responsew moich testach jednostkowych. Rozwiązałem to za pomocą wywołania response.releaseConnection(); metody releaseConnection () - Metoda jest tylko na ClientResponseobiekcie resteasy , więc musiałem dodać rzut od Responsedo ClientResponse.


To uratowało mi dzień! W moim przypadku musiałem przesłać do org.jboss.resteasy.client.jaxrs.internal.ClientResponse, aby jeszcze bardziej zawęzić.
user2081279

1

Spróbuj tego

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

Ok, mam podobny problem, wszystkie te rozwiązania nie działają, testowałem na jakimś urządzeniu, problem był datowany w urządzeniu, to był rok 2011 zamiast 2013, sprawdź też to może pomóc.


0

Przeczytaj InputStream w ten sposób:

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() + "" );
    }
}

0

po prostu skonsumuj odpowiedź jak poniżej, aby rozwiązać problem

response.getEntity().consumeContent();
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.