¿Por qué tiene que llamar a URLConnection # getInputStream para poder escribir en URLConnection # getOutputStream?

Estoy intentando escribir en URLConnection#getOutputStream ; sin embargo, no se envían datos hasta que llamo a URLConnection#getInputStream . Incluso si configuro URLConnnection#doInput en false, aún no se enviará. ¿Alguien sabe a que se debe esto? No hay nada en la documentación de la API que describa esto.

Documentación de la API de Java en URLConnection: http://download.oracle.com/javase/6/docs/api/java/net/URLConnection.html

Tutorial de Java sobre lectura y escritura en URLConnection: http://download.oracle.com/javase/tutorial/networking/urls/readingWriting.html

 import java.io.IOException; import java.io.OutputStreamWriter; import java.net.URL; import java.net.URLConnection; public class UrlConnectionTest { private static final String TEST_URL = "http://localhost:3000/test/hitme"; public static void main(String[] args) throws IOException { URLConnection urlCon = null; URL url = null; OutputStreamWriter osw = null; try { url = new URL(TEST_URL); urlCon = url.openConnection(); urlCon.setDoOutput(true); urlCon.setRequestProperty("Content-Type", "text/plain"); //////////////////////////////////////// // SETTING THIS TO FALSE DOES NOTHING // //////////////////////////////////////// // urlCon.setDoInput(false); osw = new OutputStreamWriter(urlCon.getOutputStream()); osw.write("HELLO WORLD"); osw.flush(); ///////////////////////////////////////////////// // MUST CALL THIS OTHERWISE WILL NOT WRITE OUT // ///////////////////////////////////////////////// urlCon.getInputStream(); ///////////////////////////////////////////////////////////////////////////////////////////////////////// // If getInputStream is called while doInput=false, the following exception is thrown: // // java.net.ProtocolException: Cannot read from URLConnection if doInput=false (call setDoInput(true)) // ///////////////////////////////////////////////////////////////////////////////////////////////////////// } catch (Exception e) { e.printStackTrace(); } finally { if (osw != null) { osw.close(); } } } } 

La API para URLConnection y HttpURLConnection están (para bien o para mal) diseñadas para que el usuario siga una secuencia de eventos muy específica:

  1. Establecer propiedades de solicitud
  2. (Opcional) getOutputStream (), escribe en la transmisión, cierra la transmisión
  3. getInputStream (), leer de la secuencia, cerrar la secuencia

Si su solicitud es POST o PUT, necesita el paso opcional n. ° 2.

A mi leal saber y entender, el OutputStream no es como un socket, no está conectado directamente a un InputStream en el servidor. En su lugar, después de cerrar o purgar la transmisión, Y llamar a getInputStream (), su resultado se integra en una Solicitud y se envía. La semántica se basa en la suposición de que querrá leer la respuesta. Cada ejemplo que he visto muestra este orden de eventos. Definitivamente estoy de acuerdo con usted y con otros que esta API es contradictoria en comparación con la API de E / S de transmisión normal.

El tutorial al que se vincula establece que “URLConnection es una clase centrada en HTTP”. Interpreto que eso quiere decir que los métodos están diseñados en torno a un modelo Solicitud-Respuesta, y supongo que así será como se usarán.

Por lo que vale, encontré este informe de errores que explica el funcionamiento previsto de la clase mejor que la documentación de javadoc. La evaluación del informe indica que “la única forma de enviar la solicitud es llamando a getInputStream”.

Aunque el método getInputStream () ciertamente puede hacer que un objeto URLConnection inicie una solicitud HTTP, no es un requisito hacerlo.

Considere el flujo de trabajo real:

  1. Crear una solicitud
  2. Enviar
  3. Procesar la respuesta

El paso 1 incluye la posibilidad de incluir datos en la solicitud, por medio de una entidad HTTP. Sucede que la clase URLConnection proporciona un objeto OutputStream como el mecanismo para proporcionar estos datos (y con razón por muchas razones que no son particularmente relevantes aquí). Basta con decir que la naturaleza de transmisión de este mecanismo proporciona al progtwigdor una cantidad de flexibilidad al suministrar los datos, incluida la capacidad de cerrar el flujo de salida (y cualquier flujo de entrada que lo alimente), antes de finalizar la solicitud.

En otras palabras, el paso 1 permite suministrar una entidad de datos para la solicitud y luego continuar compilándola (por ejemplo, agregando encabezados).

El paso 2 es realmente un paso virtual, y se puede automatizar (como en la clase URLConnection), ya que enviar una solicitud no tiene sentido sin una respuesta (al menos dentro de los límites del protocolo HTTP).

Lo que nos lleva al Paso 3. Cuando procesamos una respuesta HTTP, la entidad de respuesta, recuperada al llamar a getInputSteam (), es solo una de las cosas en las que podríamos estar interesados. Una respuesta consiste en un estado, encabezados y, opcionalmente, una entidad. La primera vez que se solicite cualquiera de estos, URLConnection realizará el paso virtual 2 y enviará la solicitud.

No importa si una entidad se envía a través del flujo de salida de la conexión o no, y no importa si se espera que una entidad de respuesta regrese, un progtwig SIEMPRE querrá saber el resultado (según lo provisto por el código de estado HTTP). Llamar a getResponseCode () en URLConnection proporciona este estado, y al encender el resultado puede finalizar la conversación HTTP sin llamar a getInputStream ().

Por lo tanto, si se envían datos y no se espera una entidad de respuesta, no haga esto:

 // request is now built, so... InputStream ignored = urlConnection.getInputStream(); 

… hacer esto:

 // request is now built, so... int result = urlConnection.getResponseCode(); // act based on this result 

Como mis experimentos han demostrado (java 1.7.0_01) el código:

 osw = new OutputStreamWriter(urlCon.getOutputStream()); osw.write("HELLO WORLD"); osw.flush(); 

No envía nada al servidor. Simplemente guarda lo que está escrito allí en el búfer de memoria. Por lo tanto, en caso de que va a cargar un archivo grande a través de POST, debe asegurarse de tener suficiente memoria. En el escritorio / servidor puede que no sea un problema tan grande, pero en Android puede provocar un error de memoria. Aquí está el ejemplo de cómo se ve el seguimiento de la stack cuando se intenta escribir en el flujo de salida y se agota la memoria.

 Exception in thread "Thread-488" java.lang.OutOfMemoryError: GC overhead limit exceeded at java.util.Arrays.copyOf(Arrays.java:2271) at java.io.ByteArrayOutputStream.grow(ByteArrayOutputStream.java:113) at java.io.ByteArrayOutputStream.ensureCapacity(ByteArrayOutputStream.java:93) at java.io.ByteArrayOutputStream.write(ByteArrayOutputStream.java:140) at sun.net.www.http.PosterOutputStream.write(PosterOutputStream.java:78) at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:221) at sun.nio.cs.StreamEncoder.implWrite(StreamEncoder.java:282) at sun.nio.cs.StreamEncoder.write(StreamEncoder.java:125) at sun.nio.cs.StreamEncoder.write(StreamEncoder.java:135) at java.io.OutputStreamWriter.write(OutputStreamWriter.java:220) at java.io.Writer.write(Writer.java:157) at maxela.tables.weboperations.POSTRequest.makePOST(POSTRequest.java:138) 

En la parte inferior de la traza, puede ver el método makePOST() que hace lo siguiente:

  writer = new OutputStreamWriter(conn.getOutputStream()); for (int j = 0 ; j < 3000 * 100 ; j++) { writer.write("&var" + j + "=garbagegarbagegarbage_"+ j); } writer.flush(); 

Y writer.write() arroja la excepción. Además, mis experimentos han demostrado que cualquier excepción relacionada con la conexión / IO real con el servidor se produce solo después de urlCon.getOutputStream() . Incluso urlCon.connect() parece ser un método "ficticio" que no hace ninguna conexión física. Sin embargo, si llama a urlCon.getContentLengthLong() que devuelve Content-Length: campo de encabezado de los encabezados de respuesta del servidor, se llamará automáticamente a URLConnection.getOutputStream () y, en caso de que exista una excepción, se lanzará.

Las excepciones lanzadas por urlCon.getOutputStream() son todas IOException, y me he encontrado con las siguientes:

  try { urlCon.getOutputStream(); } catch (UnknownServiceException ex) { System.out.println("UnkownServiceException():" + ex.getMessage()); } catch (ConnectException ex) { System.out.println("ConnectException()"); Logger.getLogger(POSTRequest.class.getName()).log(Level.SEVERE, null, ex); } catch (IOException ex) { System.out.println("IOException():" + ex.getMessage()); Logger.getLogger(POSTRequest.class.getName()).log(Level.SEVERE, null, ex); } 

Espero que mi pequeña investigación ayude a las personas, ya que la clase URLConnection es un poco contra-intuitiva en algunos casos, por lo tanto, al implementarla, uno necesita saber de qué se trata.

El segundo motivo es: cuando se trabaja con servidores, el trabajo con el servidor puede fallar por varias razones (conexión, DNS, firewall, httpresponses, servidor que no puede aceptar la conexión, servidor que no puede procesar la solicitud a tiempo). Por lo tanto, es importante entender cómo las excepciones planteadas pueden explicar acerca de lo que realmente está sucediendo con la conexión .

La llamada a getInputStream () indica que el cliente ha terminado de enviar su solicitud, y está listo para recibir la respuesta (por especificación de HTTP). Parece que la clase URLConnection tiene esta noción incorporada, y debe estar al ras () de la stream de salida cuando se solicita la stream de entrada.

Como señaló el otro respondedor, deberías poder llamar a flush () para activar la escritura.

La razón fundamental es que tiene que calcular un encabezado de longitud del contenido automáticamente (a menos que esté utilizando el modo de transmisión por secuencias o por secuencias). No puede hacer eso hasta que haya visto toda la salida, y tiene que enviarla antes de la salida, por lo que debe almacenar la salida. Y necesita un evento decisivo para saber cuándo se ha escrito realmente el último resultado. Entonces usa getInputStream () para eso. En ese momento escribe los encabezados, incluida la longitud del contenido, luego la salida, luego comienza a leer la entrada.

(Reconsidere de su primera pregunta. Self-plug desvergonzado) No juegue con URLConnection usted mismo, deje que Resty lo maneje.

Aquí está el código que necesitaría para escribir (supongo que recibirá el texto):

 import static us.monoid.web.Resty.*; import us.monoid.web.Resty; ... new Resty().text(TEST_URL, content("HELLO WORLD")).toString();