2011-01-30 17 views
27

Estoy tratando de escribir a URLConnection#getOutputStream, sin embargo, no se envían datos hasta que llamo al URLConnection#getInputStream. Incluso si establezco URLConnnection#doInput en falso, 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.¿Por qué tiene que llamar a URLConnection # getInputStream para poder escribir en URLConnection # getOutputStream?

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

Tutorial de Java en la lectura y escritura a un 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(); 
      } 
     } 

    } 

} 

Respuesta

34

El API para URLConnection y HttpURLConnection son (para mejor o para peor) diseñado para que el usuario siga una secuencia específica de eventos:

  1. Conjunto propiedades de la solicitud
  2. (Opcional) getOutputStream(), escribir en la secuencia, cierre la corriente
  3. getInputStream(), lea de la secuencia, cierre la secuencia

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

Por lo que yo sé, 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 bug report 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".

+0

¡Buen hallazgo en el error! Eso realmente aclara las cosas y me alegra saber que ha sido documentado en alguna parte. ¡Gracias! – John

+0

Java 8 todavía tiene esta característica de error/no documentada, como acabo de descubrir. (Encontré esta página mientras buscaba una solución o solución a este problema). ¿Hay una mejor alternativa ahora, más de seis años después de que se hizo la pregunta original? –

1

Calling getInputStream() señales de que el cliente está terminado de enviar el mismo de la petición, y está listo para recibir la respuesta (por especificación HTTP). Parece que la clase URLConnection tiene esta noción incorporada, y debe estar al ras() de la corriente de salida cuando se solicita la corriente de entrada.

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

+0

Hola, gracias por James los detalles con respecto a la especificación de HTTP. Esa respuesta es la línea de lo que estoy buscando. Entonces, ¿es porque la especificación HTTP exige que debe haber una respuesta para que sea una solicitud? En cuanto al uso de 'flush()', verá que lo intenté y no vi una solicitud en el otro extremo. – John

+0

También asegúrese de obtener las transmisiones en el orden correcto, llamando a la descarga en el proceso. Si no se enjuaga después de la obtención inicial, los encabezados de comunicación no se transferirán entre las partes y, finalmente, se producirá un punto muerto. – pnt

+0

Entonces 'getOuputStream()', 'flush()', 'write()', y finalmente 'flush()' de nuevo? – John

-3

(Repita desde su primera pregunta. Autoenchufe descarado) No juguetee con URLConnection usted mismo, deje que Resty lo manipule.

Aquí está el código que se necesita para escribir (supongo que está recibiendo texto de nuevo):

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

La razón fundamental es que tiene que calcular automáticamente un encabezado Content-length (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.

+0

No tiene que calcular Content-Length automáticamente. Puede llamar a setFixedLengthStreamingMode para darle la longitud. El almacenamiento en búfer interno se desactivará. – vocaro

+0

@vocaro estuvo de acuerdo, pero el OP no está haciendo eso, que es el caso que abordé, y que explica el comportamiento que está viendo. – EJP

+1

¿No se cerraría también la secuencia de salida para que no fuera necesario llamar a getInputStream? –

2

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 pila 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 se puede ver el método makePOST() el 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() produce la excepción. También mis experimentos han demostrado que cualquier excepción relacionada con la conexión/IO real con el servidor se produce solo después de llamar al urlCon.getOutputStream(). Incluso urlCon.connect() parece ser un método "ficticio" que no hace ninguna conexión física. Sin embargo, si llama al urlCon.getContentLengthLong() que devuelve Content-Length: campo de encabezado de los encabezados de respuesta del servidor, entonces 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 todos IOException, y he conocido a los siguientes aparatos:

   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 ayuda a la gente, como la clase URLConnection es un poco contrario a la intuición, en algunos casos, por lo tanto, al implementarlo, uno necesita saber de qué se trata.

La segunda razón es la siguiente: cuando se trabaja con servidores - el trabajo con el servidor puede fallar debido a muchas razones (conexión, DNS, firewall, httpresponses, el servidor no ser capaz de aceptar la conexión, el servidor no ser capaz de procesar la solicitud oportuna). Por lo tanto, es importante comprender cómo las excepciones planteadas pueden explicar lo que está sucediendo realmente con la conexión.

3

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. Construir una solicitud
  2. Presentar
  3. Proceso de la respuesta

Paso 1 incluye la posibilidad de incluir los 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 programador 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 puede automatizarse (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 llamando a getInputSteam(), es solo una de las cosas que nos pueden interesar. 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 una entidad de respuesta, un programa 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().

lo tanto, si se presenta de datos, y no se espera una respuesta de la entidad, no hacer 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 
Cuestiones relacionadas