2011-01-22 14 views
35

Al utilizar HttpURLConnection, ¿se debe cerrar el InputStream si no lo'gramos 'y lo usamos?Uso seguro de HttpURLConnection

i.e. ¿esto es seguro?

HttpURLConnection conn = (HttpURLConnection) uri.getURI().toURL().openConnection(); 
conn.connect(); 
// check for content type I don't care about 
if (conn.getContentType.equals("image/gif") return; 
// get stream and read from it 
InputStream is = conn.getInputStream(); 
try { 
    // read from is 
} finally { 
    is.close(); 
} 

En segundo lugar, ¿es seguro para cerrar un InputStream antes de que todo su contenido ha sido leído completamente?

¿Existe el riesgo de dejar el socket subyacente en estado ESTABLISHED o incluso CLOSE_WAIT?

Respuesta

25

¿Es seguro para cerrar un InputStream antes de que todo su contenido ha sido leer

Es necesario leer todos los datos en el flujo de entrada antes de cerrar de modo que la conexión TCP subyacente se almacena en caché. He leído que no debería ser necesario en la última versión de Java, pero siempre fue obligatorio leer toda la respuesta para la reutilización de la conexión.

Comprobar este post: keep-alive in java6

+0

Muy interesante. Esta pregunta es en realidad de fondo para un problema que tengo en el que veo MUCHOS de sockets CLOSE_WAIT en la misma IP, pero debido al almacenamiento en caché (no llamo URLConnection.disconnect() explícitamente) espero que haya solo uno, que debe ser reutilizado – Joel

+3

@Joel: Al llamar al 'HttpUrlConnection.disconnect()', se cierra el zócalo tcp subyacente. Al cerrar el flujo de entrada, el zócalo tcp subyacente se agrupa para su reutilización posterior.La única advertencia es que toda la respuesta (O toda la respuesta de error) debe leerse desde la secuencia de entrada para que la conexión tcp se almacene en caché. Esto siempre se ha asesorado independientemente de que realmente necesite toda la información de la transmisión. Revise la publicación en mi respuesta – Cratylus

+0

¿Qué sucede si no lee CUALQUIER dato, como en mi primer ejemplo? Supongo que todavía necesita cerrar el IS, pero ¿no se almacenará en caché si no se leen los datos, pero sigue siendo cerrado. – Joel

1

Al utilizar HttpURLConnection, ¿se debe cerrar el InputStream si no lo'gramos 'y lo usamos?

Sí, siempre debe estar cerrado.

i.e. ¿es esto seguro?

No es 100%, corre el riesgo de obtener un NPE. Más seguro es:

InputStream is = null; 
try { 
    is = conn.getInputStream() 
    // read from is 
} finally { 
    if (is != null) { 
     is.close(); 
    } 
} 
+1

La segunda pregunta fue con respecto al estado del socket subyacente, he publicado deliberadamente un fragmento incompleto con respecto a la seguridad del código de tiempo de ejecución completo. Realmente quiero saber si existe el peligro de que quede un socket en CLOSE_WAIT o ESTABLISED cerrando el socket antes de leer todo el contenido, – Joel

+0

O 'IOUtils.closeQuietly (is)' – Kirby

6

Si realmente quiere asegurarse de que la conexión está cerca debe llamar conn.disconnect().

Las conexiones abiertas que ha observado se deben a la función de mantenimiento de conexión HTTP 1.1 (también conocida como HTTP Persistent Connections). Si el servidor es compatible con HTTP 1.1 y no envía un Connection: close en el encabezado de respuesta, Java no cierra inmediatamente la conexión TCP infrautilizada cuando cierra la secuencia de entrada. En cambio, lo mantiene abierto y trata de reutilizarlo para la siguiente solicitud HTTP al mismo servidor.

Si no desea que este comportamiento en absoluto se puede establecer la propiedad del sistema http.keepAlive false:

System.setProperty("http.keepAlive","false"); 
+1

Gracias. Suponiendo que la conexión no está en uso, ¿sabe por cuánto tiempo se almacena en caché antes de que se cierre y existe alguna forma de controlar este período de tiempo de espera? – Joel

14

Aquí hay alguna información con respecto a la caché de mantenimiento de conexión. Toda esta información pertenece a Java 6, pero probablemente también sea precisa para muchas versiones anteriores y posteriores.

De lo que puedo decir, el código se reduce a:

  1. Si el servidor remoto envía un encabezado "Keep-Alive" con un valor de "tiempo de espera" que puede ser analizada como un entero positivo, que número de segundos se usa para el tiempo de espera.
  2. Si el servidor remoto envía un encabezado "Keep-Alive" pero no tiene un valor de "timeout" que se puede analizar como un entero positivo y "usingProxy" es verdadero, entonces el tiempo de espera es de 60 segundos.
  3. En todos los demás casos, el tiempo de espera es de 5 segundos.

Esta lógica se divide entre dos lugares: alrededor de la línea 725 de sun.net.www.http.HttpClient (en el método "parseHTTPHeader"), y alrededor de la línea 120 de sun.net.www.http.KeepAliveCache (en el método de "poner").


Por lo tanto, hay dos maneras de controlar el tiempo de espera:

  1. de control del servidor remoto y configurarlo para enviar un encabezado Keep-Alive con el campo de tiempo de espera adecuado
  2. Modificar el JDK código fuente y crea el tuyo

Uno pensaría que sería posible cambiar el valor por defecto aparentemente arbitrario de cinco segundos sin recompilar las clases internas de JDK, pero no lo es. Un bug se presentó en 2005 solicitando esta capacidad, pero Sun se negó a proporcionarla.

+3

Buena investigación sobre un tema pobremente documentado. Gracias por compartir. –

31

Según http://docs.oracle.com/javase/6/docs/technotes/guides/net/http-keepalive.html y código fuente OpenJDK.

(Cuando keepAlive == true)

Si el cliente llama HttpURLConnection.getInputSteam(). cerrar(), la última llamada a HttpURLConnection. desconecte() NO cierre el zócalo. es decir, el socket se reutiliza (en caché)

Si el cliente no llama a close(), call disconnect() cerrará el InputSteam y cerrará el socket.

Para reutilizar el Socket, simplemente llame a InputStream close(). No llame a HttpURLConnection disconnect().

2

También hay que cerrar flujo de error si falla la petición HTTP (cualquier cosa menos 200):

try { 
    ... 
} 
catch (IOException e) { 
    connection.getErrorStream().close(); 
} 

Si no lo hace, todas las solicitudes que no devuelven 200 (por ejemplo, tiempo de espera) será gotear un socket. Vea http://scotte.github.io/2015/01/httpurlconnection-socket-leak/ para más detalles.

+1

No estoy seguro de eso: el último código fuente (JDK 8u74) lee 'public InputStream getErrorStream() { return null; } ' – FelixJongleur42

Cuestiones relacionadas