2011-01-30 6 views
27

Sto cercando di scrivere su URLConnection#getOutputStream, tuttavia, nessun dato viene effettivamente inviato fino a quando non chiamo URLConnection#getInputStream. Anche se ho impostato URLConnnection#doInput su false, non verrà ancora inviato. qualcuno sà perche è cosi? Non c'è nulla nella documentazione dell'API che lo descriva.Perché devi chiamare URLConnection # getInputStream per poter scrivere su URLConnection # getOutputStream?

Java API Documentazione sulla URLConnection: http://download.oracle.com/javase/6/docs/api/java/net/URLConnection.html

Tutorial di Java sulla lettura e la scrittura ad 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(); 
      } 
     } 

    } 

} 

risposta

34

L'API per URLConnection e HttpURLConnection sono (bene o male) progettato per l'utente a seguire un specifica sequenza di eventi:

  1. Imposta proprietà della richiesta di
  2. (opzionale) getOutputStream(), scrivere nel flusso, chiudere il flusso
  3. getInputStream(), leggere dal flusso, chiudere il flusso

Se la tua richiesta è un POST o PUT, è necessario il passaggio facoltativo 2 #.

Per quanto ne so, l'OutputStream non è come un socket, non è direttamente collegato a un InputStream sul server. Invece, dopo aver chiuso o scaricato lo stream, e chiamare getInputStream(), l'output è incorporato in una richiesta e inviato. La semantica si basa sul presupposto che vorrete leggere la risposta. Ogni esempio che ho visto mostra questo ordine di eventi. Sarei certamente d'accordo con te e con gli altri sul fatto che questa API è controintuitiva rispetto alla normale API di I/O del flusso.

Il tutorial si collega a stati che "URLConnection è una classe HTTP-centrica". Interpreto questo per significare che i metodi sono progettati attorno a un modello di risposta alla richiesta e fanno presupporre che saranno utilizzati.

Per quello che vale, ho trovato questo bug report che spiega l'operazione prevista della classe meglio della documentazione di javadoc. La valutazione del rapporto afferma "L'unico modo per inviare la richiesta è chiamando getInputStream."

+0

Buona scoperta sul bug! Questo chiarisce le cose e sono felice di sapere che è stato documentato da qualche parte. Grazie! – John

+0

Java 8 ha ancora questo bug/funzionalità non documentata, come ho appena scoperto. (Ho trovato questa pagina mentre cercavo una soluzione o una soluzione alternativa a questo problema.) Esiste un'alternativa migliore ora, oltre sei anni dopo la domanda originale? –

1

Calling getInputStream() segnala che il cliente è terminato l'invio di essa la richiesta, ed è pronto a ricevere la risposta (per specifica HTTP). Sembra che la classe URLConnection abbia questa nozione incorporata e deve essere flush() nel flusso di output quando viene richiesto lo stream di input.

Come ha notato l'altro rispondente, si dovrebbe essere in grado di chiamare flush() per attivare la scrittura.

+0

Ciao James, grazie per i dettagli riguardanti le specifiche HTTP Questa risposta è la linea di ciò che sto cercando. Quindi, perché la specifica HTTP impone che ci deve essere una risposta per il loro essere una richiesta? Per quanto riguarda l'uso di 'flush()', vedrete che l'ho provato e non ho visto una richiesta venire dall'altra parte. – John

+0

Assicurati inoltre di ottenere gli stream nell'ordine corretto, chiamando flush nel processo. Se non si riesce a svuotare dopo l'acquisizione iniziale, le intestazioni delle comunicazioni non verranno trasferite tra le parti e, in definitiva, un deadlock. – pnt

+0

Quindi 'getOuputStream()', 'flush()', 'write()', e infine 'flush()' di nuovo? – John

-3

(Ripubblicazione dalla prima domanda. Auto-plug svergognata) Non giocherellare con URLConnection, lasciare che sia Resty a gestirlo.

Ecco il codice si avrebbe bisogno di scrivere (suppongo che si stanno ottenendo il testo di nuovo):

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

La ragione fondamentale è che ha a calcolare automaticamente un header Content-length (a meno che non si utilizza la modalità Chunked o streaming). Non può farlo finché non ha visto tutto l'output e deve inviarlo prima dell'output, quindi deve bufferizzare l'output.E ha bisogno di un evento decisivo per sapere quando l'ultimo output è stato effettivamente scritto. Quindi usa getInputStream() per quello. A quel tempo scrive le intestazioni includendo la lunghezza del contenuto, quindi l'output, quindi inizia a leggere l'input.

+0

Non deve calcolare automaticamente Content-Length. È possibile chiamare setFixedLengthStreamingMode per assegnargli la lunghezza. Il buffering interno sarà quindi disabilitato. – vocaro

+0

@vocaro è d'accordo ma l'OP non lo sta facendo, che è il caso che ho affrontato, e che spiega il comportamento che sta vedendo. – EJP

+1

Anche la chiusura del flusso di output non dovrebbe essere eseguita, quindi la chiamata a getInputStream non sarebbe necessaria? –

2

Come i miei esperimenti hanno dimostrato (Java 1.7.0_01) il codice:

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

non invia nulla al server. Salva semplicemente ciò che è scritto lì nel buffer di memoria. Pertanto, nel caso in cui si debba caricare un file di grandi dimensioni tramite POST, è necessario essere sicuri di disporre di memoria sufficiente. Su desktop/server potrebbe non essere un grosso problema, ma su Android potrebbe causare errori di memoria insufficiente. Ecco l'esempio di come appare la traccia dello stack quando si tenta di scrivere sul flusso di output e la memoria si esaurisce.

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) 

Sul fondo della traccia si può vedere il metodo makePOST(), che esegue le seguenti operazioni:

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

E writer.write() genera l'eccezione. Anche i miei esperimenti hanno mostrato che qualsiasi eccezione relativa alla connessione/I/O effettiva con il server viene lanciata solo dopo che è stato chiamato urlCon.getOutputStream(). Anche urlCon.connect() sembra essere un metodo "fittizio" che non esegue alcuna connessione fisica. Tuttavia, se si chiama urlCon.getContentLengthLong() che restituisce Content-Length: campo dell'intestazione dalle intestazioni di risposta del server, URLConnection.getOutputStream() verrà chiamato automaticamente e nel caso in cui ci sia un'eccezione, verrà generato.

Le eccezioni generate dal urlCon.getOutputStream() sono tutti IOException, e mi hanno incontrato quelli follwing:

   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); 
       } 

Spero che la mia piccola ricerca aiuta a persone, come classe URLConnection è un po 'contro-intuitivo, in alcuni casi, quindi, quando lo si implementa, è necessario sapere con cosa si tratta.

Il secondo motivo è: quando si lavora con i server - il lavoro con il server può fallire a causa di molti motivi (connessione, dns, firewall, httpresponses, il server non è in grado di accettare la connessione, il server non è in grado di elaborare la richiesta tempestivamente). Pertanto, è importante comprendere in che modo le eccezioni sollevate possono spiegare cosa sta effettivamente accadendo con la connessione.

3

Sebbene il metodo getInputStream() possa certamente causare l'avvio di una richiesta HTTP da parte di un oggetto URLConnection, non è obbligatorio farlo.

consideri il flusso di lavoro attuale:

  1. Costruire una richiesta
  2. Invia
  3. processo la risposta

Fase 1 prevede la possibilità di inserire i dati nella richiesta, per mezzo di un Entità HTTP. Accade solo che la classe URLConnection fornisca un oggetto OutputStream come meccanismo per fornire questi dati (e giustamente per molti motivi che non sono particolarmente rilevanti qui). Basti dire che la natura di streaming di questo meccanismo fornisce al programmatore una certa flessibilità nel fornire i dati, inclusa la possibilità di chiudere il flusso di output (e qualsiasi flusso di input che lo alimenta), prima di terminare la richiesta.

In altre parole, il passaggio 1 consente di fornire un'entità dati per la richiesta, quindi di continuare a crearlo (ad esempio aggiungendo le intestazioni).

Il passaggio 2 è in realtà un passo virtuale e può essere automatizzato (come nella classe URLConnection), poiché l'invio di una richiesta non ha senso senza una risposta (almeno entro i confini del protocollo HTTP).

Che ci porta al passaggio 3. Quando si elabora una risposta HTTP, l'entità di risposta, richiamata da getInputSteam(), è solo una delle cose a cui potremmo essere interessati. Una risposta consiste in uno stato, intestazioni e facoltativamente un'entità. La prima volta che viene richiesto uno di questi, URLConnection eseguirà il passaggio virtuale 2 e inoltrerà la richiesta.

Indipendentemente dal fatto che un'entità venga inviata tramite il flusso di output della connessione o meno, e indipendentemente dall'erogazione di un'entità di risposta, un programma SEMPRE desidera conoscere il risultato (come previsto dal codice di stato HTTP). La chiamata a getResponseCode() su URLConnection fornisce questo stato e l'attivazione del risultato può terminare la conversazione HTTP senza mai chiamare getInputStream().

Quindi, se i dati è presentata, e un'entità di risposta non è previsto, non farlo:

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

... fare questo:

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