Excepción al usar HttpRequest.execute (): uso no válido de SingleClientConnManager: conexión aún asignada

81

Estoy usando google-api-client-java 1.2.1-alpha para ejecutar una solicitud POST, y obtengo el siguiente seguimiento de pila cuando ejecuto () HttpRequest.

Ocurre inmediatamente después de detectar e ignorar un error 403 de un POST anterior a la misma URL y reutilizar el transporte para la solicitud posterior. (Está en un bucle que inserta varias entradas en la misma alimentación ATOM).

¿Hay algo que deba hacer para 'limpiar' después de un 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)

¿Por qué el código debajo de mí intentaría adquirir una nueva conexión?

David Bullock
fuente
Esto todavía parece ser un problema con la versión 1.11.0-beta: /
sjngm
5
Para el beneficio de cualquiera que llegue aquí después de intentar consumir las respuestas y seguir recibiendo las advertencias, encontré la respuesta correcta aquí: tech.chitgoks.com/2011/05/05/…
Steelight
@Steelight: el uso del enfoque tech.chitgoks.com resolvió mi problema.
Cale Sweeney

Respuestas:

82

Debe consumir el cuerpo de la respuesta antes de poder reutilizar la conexión para otra solicitud. No solo debe leer el estado de la respuesta, sino leer la respuesta por InputStreamcompleto hasta el último byte, por lo que simplemente ignora los bytes leídos.

BalusC
fuente
1
¡Eso fue todo! En el caso de google-api-java-client, esto significaba atrapar el IOExceptionarrojado HttpResponse.execute(), probarlo / transmitirlo HttpResponseException, acceder al responsemiembro y luego invocarlo paraseAsString(). (Que resultó ser información útil de todos modos: -)
David Bullock
5
También hay un método HttpEntity.consumeContent () para descartar el contenido.
Grzegorz Adam Hankiewicz
3
EntityUtils.consume (entidad): consumeContent ahora está en desuso.
David Carboni
Solo para tener en cuenta que a partir de las versiones recientes de Google Http Java Client (al menos 1.16, pero posiblemente antes), la llamada HttpRequest.execute()limpiará automáticamente estos recursos si el método no devuelve un HttpResponseobjeto. Además, JavaDoc de HttpResponseahora recomienda llamar response.disconnect()en caso de no leer completamente el contenido de la respuesta HTTP (en un finallybloque).
David Bullock
El mismo problema con RestEasy 3.0.4 que usa internamente BasicClientConnectionManager de apache-httpclient 4.2.1. Gracias @BalusC. Además, nunca leí nada sobre leer la transmisión por completo, ¿dónde debería uno mirar cuando se trata de ser consciente de estos escollos? (en cuanto a documentación, que no sea el código fuente)
panel
42

Me enfrentaba a un problema similar al usar HttpClient con Jetty para crear un marco de prueba. Tuve que crear múltiples solicitudes al Servelet desde mi cliente, pero estaba dando la misma excepción cuando se ejecutó.

Encontré una alternativa en http://foo.jasonhudgins.com/2010/03/http-connections-revisited.html

También puede utilizar este método siguiente para crear una instancia de su cliente.

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;
}
Ujjwal Wadhawan
fuente
Gracias, funcionó bastante bien. Sin embargo, tengo curiosidad por la necesidad de crear dos DefaultHttpClients
htafoya
2
Utiliza HttpParams predeterminados (los obtiene del cliente) en lugar de crear uno propio desde cero.
Marcin Gil
Eso resolvió mi problema, pero ¿está bien usar esto en la aplicación de Android?
Manish
Curiosamente, eso funciona parcialmente para mí, termina bloqueando la ejecución del código restante cuando hago varias solicitudes HTTP en un bucle y no consumo las respuestas. Y tampoco fallé, no esperé demasiado para ver si se produce un error de tiempo de espera. Entonces, en cambio, tomé la ruta de asignar nuevos DefaultHttpClients para cada solicitud y eso funcionó para mí, ya que no ejecuto el ciclo por mucho tiempo.
David
9

Un mensaje de excepción similar (ya que al menos Apache Jarkata Commons HTTP Client 4.2) es:

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

Esta excepción puede ocurrir cuando dos o más subprocesos interactúan con uno org.apache.http.impl.client.DefaultHttpClient.

¿Cómo puede hacer que una DefaultHttpClientinstancia 4.2 sea segura para subprocesos ( segura para subprocesos en el sentido de que dos o más subprocesos pueden interactuar con ella sin obtener el mensaje de error anterior)? Proporcionar DefaultHttpClientun pool de conexiones ClientConnectionManageren forma de 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);
        }
    }
}
Abdull
fuente
8

Ésta es una pregunta frecuente. La respuesta de BalusC es correcta. Por favor, coger HttpReponseException , y llamar a HttpResponseException. respuesta . ignorar (). Si necesita leer el mensaje de error, use response. parseAsString () si no conoce el tipo de contenido de la respuesta, de lo contrario, si conoce el tipo de contenido, use la respuesta. parseAs (MyType.class).

Un simple fragmento de código de YouTubeSample.java en youtube-jsonc-sample (aunque normalmente querrás hacer algo más inteligente en una aplicación real):

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

Divulgación completa: soy propietario del proyecto google-api-java-client .

Yaniv Inbar
fuente
15
El hecho de que esta sea una pregunta frecuente, ¿no sugiere que existe un problema de usabilidad en el diseño de su biblioteca?
cordura
@sanity O tal vez en la propia pila http;)
krosenvold
3

Tuve el mismo problema con un Responseobjeto jax-rs (resteasy) en mis pruebas unitarias. response.releaseConnection(); Resolví esto con una llamada a The releaseConnection () - El método está solo en el ClientResponseobjeto resteasy , así que tuve que agregar un elenco de Responsea ClientResponse.

Markus
fuente
¡Eso me salvó el día! En mi caso, tuve que enviar a org.jboss.resteasy.client.jaxrs.internal.ClientResponse para reducirlo aún más.
user2081279
1

Prueba esto

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");
    }
Silambarasan Poonguti
fuente
0

Ok, tengo un problema similar, todas esas soluciones no funcionan, probé en algún dispositivo, el problema fue la fecha en el dispositivo, fue 2011 en lugar de 2013, verifique también que esto puede ayudar.

marko
fuente
0

Lea InputStream así:

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

solo consume la respuesta como a continuación, eso resolverá el problema

response.getEntity().consumeContent();
Mohammed Rafeeq
fuente