2009-12-20 13 views
5

Estamos utilizando HttpURLConnection API para invocar una API REST al mismo proveedor a menudo (tipo de uso de agregación). Queremos mantener un grupo de 5 conexiones siempre abiertas para el host del proveedor (siempre la misma IP).cómo mantener múltiples Java HttpConnections abiertas al mismo destino

¿Cuál es la solución adecuada? Esto es lo que tratamos:


System.setProperty("http.maxConnections", 5); // set globally only once 
... 
// everytime we need a connection, we use the following 
HttpURLConnection conn = (HttpURLConnection) (new URL(url)).openConnection(); 
conn.setRequestMethod("GET"); 
conn.setDoInput(true); 
conn.setDoOutput(false); 
conn.setUseCaches(true); 
... 
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream())); 
... 

En este punto, leemos el flujo de entrada hasta que el BufferedReader no devuelve nada más bytes. ¿Qué hacemos después de ese punto si queremos reutilizar la conexión subyacente al proveedor? Teníamos la impresión de que si la secuencia de entrada se leía por completo, la conexión se vuelve a agregar al grupo.

Se ha estado trabajando durante varias semanas de esta manera, pero hoy en día que dejó de funcionar producir esta excepción: java.net.SocketException: Too many open files

Encontramos numerosas tomas en el estado CLOSE_WAIT así (mediante la ejecución de lsof): java 1814 root 97u IPv6 844702 TCP colinux:58517->123.123.254.205:www (CLOSE_WAIT)

Won ¿Conn.getInputStream(). close() o conn.disconnect() cierran completamente la conexión y la eliminan del grupo?

Respuesta

4

De here:

La implementación actual no almacena el cuerpo de la respuesta. Lo que significa que la aplicación tiene que terminar de leer el cuerpo de la respuesta o llamar a close() para abandonar el resto del cuerpo de la respuesta, para que esa conexión pueda reutilizarse. Además, la implementación actual no intentará leer bloques al limpiar la conexión, lo que significa que si el cuerpo de respuesta completo no está disponible, la conexión no se reutilizará.

He leído esto como si su solución funcionara, pero que también es libre de llamar y la conexión seguirá siendo reutilizada.

4

Tuvimos este problema también en Java 5 y nuestra solución es cambiar a Apache HttpClient con un administrador de conexión mancomunado.

La implementación keepalive del manejador de URL de Sun para HTTP es muy defectuosa. No hay hilo de mantenimiento para cerrar conexiones inactivas.

Otro problema mayor con keepalive es que debe eliminar las respuestas. De lo contrario, la conexión también quedará huérfana. La mayoría de las personas no manejan la secuencia de errores correctamente. Por favor, vea mi respuesta a esta pregunta para ver un ejemplo sobre cómo leer las respuestas de error correctamente,

HttpURLConnection.getResponseCode() returns -1 on second invocation

+0

Gracias, tienes razón, no estábamos leyendo el flujo de error en caso de fallo. ¿Qué pasa con 'stream.close()' y 'conn.disconnect()'? ¿Están bien para usar o van a derrotar el propósito de http mantener vivo? Leí en algún lado cuando escribí el código que debería leer la secuencia por completo, pero no cerrarla, pero no puedo encontrar la atmósfera de referencia. – Lightbeard

+0

No debe cerrar la transmisión ni desconectar la conexión si desea reutilizar la conexión. Como dije, todavía puede haber fugas de socket incluso si haces todo bien, al menos en Java 5. –

2

El reference citado por disown fue lo que realmente ayudó.

Sabemos que Apache HttpClient es mejor, pero que requeriría otro jar y podríamos usar este código en un applet.

Llamando HttpURLConnection.connect() no fue necesario. No estoy seguro de si previene la reutilización de la conexión, pero la sacamos. Es seguro cerrar la secuencia, pero llamar al disconnect() en la conexión evitará la reutilización. Además, configurar sun.net.http.errorstream.enableBuffering=true ayuda.

Esto es lo que terminamos usando:


System.setProperty("http.maxConnections", String.valueOf(CONST.CONNECTION_LIMIT)); 
System.setProperty("sun.net.http.errorstream.enableBuffering", "true"); 

... 

int responseCode = -1; 
HttpURLConnection conn = null; 
BufferedReader reader = null; 
try { 
conn = (HttpURLConnection) (new URL(url)).openConnection(); 
conn.setRequestProperty("Accept-Encoding", "gzip"); 

// this blocks until the connection responds 
InputStream in = new GZIPInputStream(conn.getInputStream()); 

reader = new BufferedReader(new InputStreamReader(in)); 
StringBuffer sb = new StringBuffer(); 
char[] buff = new char[CONST.HTTP_BUFFER_SIZE]; 
int cnt; 

while((cnt = reader.read(buff)) > 0) sb.append(buff, 0, cnt); 

reader.close(); 

responseCode = conn.getResponseCode(); 
if(responseCode != HttpURLConnection.HTTP_OK) throw new IOException("abnormal HTTP response code:"+responseCode); 

return sb.toString(); 

} catch(IOException e) { 
    // consume error stream, otherwise, connection won't be reused 
    if(conn != null) { 
    try { 
     InputStream in = ((HttpURLConnection)conn).getErrorStream(); 
     in.close(); 
     if(reader != null) reader.close(); 
    } catch(IOException ex) { 
     log.fine(ex); 
    } 
    } 

    // log exception  
    String rc = (responseCode == -1) ? "unknown" : ""+responseCode; 
    log.severe("Error for HttpUtil.httpGet("+url+")\nServer returned an HTTP response code of '"+rc+"'"); 
    log.severe(e); 
} 
Cuestiones relacionadas