2011-01-30 9 views
27

Próbuję napisać na numer URLConnection#getOutputStream, jednak żadne dane nie zostaną wysłane, dopóki nie zadzwonię pod numer URLConnection#getInputStream. Nawet jeśli ustawię URLConnnection#doInput na false, nadal nie będzie wysyłane. Czy ktoś wie, dlaczego tak jest? W dokumentacji API, która to opisuje, nie ma nic.Dlaczego trzeba wywołać metodę URLConnection # getInputStream, aby móc pisać na adres URLConnection # getOutputStream?

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

Java Tutorial na odczyt i zapis do 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(); 
      } 
     } 

    } 

} 

Odpowiedz

34

API dla URLConnection i HttpURLConnection są (na lepsze lub gorsze), przeznaczony dla użytkownika, aby postępować bardzo specyficzna sekwencja zdarzeń:

  1. zestawu żądań Właściwości
  2. (opcjonalnie) getOutputStream(), napisz do potoku, zamknąć strumienia
  3. getInputStream(), odczyt ze strumienia, zamknij strumień

Jeśli twoje żądanie to POST lub PUT, potrzebujesz opcjonalnego kroku nr 2.

Zgodnie z moją wiedzą, OutputStream nie jest jak gniazdo, nie jest bezpośrednio podłączony do InputStream na serwerze. Zamiast tego po zamknięciu lub przepłukaniu strumienia ORAZ wywołaniu funkcji getInputStream() dane wyjściowe są wbudowane w żądanie i wysłane. Semantyka opiera się na założeniu, że będziesz chciał przeczytać odpowiedź. Każdy przykład, który widziałem, pokazuje tę kolejność wydarzeń. Z pewnością zgodziłbym się z Tobą i innymi, że ten interfejs API jest sprzeczny z intuicją w porównaniu do normalnego interfejsu I/O API.

Link do: "URLConnection jest klasą HTTP-centric". Interpretuję, że oznacza to, że metody są zaprojektowane wokół modelu reakcji na żądanie i przyjmują założenie, w jaki sposób będą używane.

Co jest warte, znalazłem ten bug report, który wyjaśnia zamierzoną operację klasy lepiej niż dokumentacja javadoc. Ocena raportu stwierdza: "Jedynym sposobem na wysłanie żądania jest wywołanie metody getInputStream".

+0

Dobre znalezisko na błąd! To naprawdę oczyszcza sprawy i cieszę się, że wiem, że zostało to gdzieś udokumentowane. Dzięki! – John

+0

Java 8 nadal ma ten błąd/nieudokumentowaną funkcję, jak właśnie odkryłem. (Znalazłem tę stronę, szukając rozwiązania lub obejścia tego problemu.) Czy jest teraz lepsza alternatywa, ponad sześć lat po zadaniu pierwotnego pytania? –

1

Wywołanie getInputStream() wskazuje, że klient jest gotowy wysłaniem wniosek, i jest gotowy do otrzymania odpowiedzi (zgodnie ze specyfikacją HTTP). Wygląda na to, że klasa URLConnection ma wbudowane to pojęcie i musi być opróżniana() w strumieniu wyjściowym, gdy żądany jest strumień wejściowy.

Jak zauważył inny respondent, powinieneś móc wywołać funkcję flush() samodzielnie, aby uruchomić zapis.

+0

Cześć James, dzięki za szczegóły dotyczące specyfikacji HTTP. Ta odpowiedź jest zgodna z tym, czego szukam. Czy to dlatego, że specyfikacja HTTP nakazuje, że musi istnieć odpowiedź na żądanie? Jeśli chodzi o użycie 'flush()', zobaczysz, że próbowałem go i nie widziałem prośby, która została wywołana na drugim końcu. – John

+0

Upewnij się również, że otrzymujesz strumienie we właściwej kolejności, wywołując w tym procesie kolor. Brak spłukiwania po początkowym pobraniu spowoduje, że nagłówki komunikatów nie zostaną przeniesione między strony, a ostatecznie - zakleszczenie. – pnt

+0

Więc 'getOuputStream()', 'flush()', 'write()', a na końcu 'flush()' ponownie? – John

-3

(Ponownie od pierwszego pytania Bezwstydna wtyczka samodzielna) Nie należy bawić się samemu przy pomocy połączenia URLConn, niech Resty poradzi sobie z tym.

Oto kod trzeba by napisać (Zakładam, że są coraz tekst z powrotem):

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

Zasadniczy powód polega na tym, że musi on automatycznie obliczyć nagłówek długości treści (chyba, że ​​korzystasz z trybu porcji lub strumieniowania). Nie może tego zrobić, dopóki nie zobaczy całego wyjścia i musi wysłać je przed wyjściem, więc musi buforować wyjście.I potrzebuje decydującego zdarzenia, aby wiedzieć, kiedy zostało napisane ostatnie wyjście. Więc używa getInputStream() do tego. W tym czasie zapisuje nagłówki, w tym długość treści, następnie dane wyjściowe, a następnie rozpoczyna odczytywanie danych wejściowych.

+0

Nie musi automatycznie obliczać długości treści. Możesz wywołać setFixedLengthStreamingMode, aby nadać mu długość. Buforowanie wewnętrzne zostanie wówczas wyłączone. – vocaro

+0

@vocaro zgodził się, ale PO nie robi tego, o czym mówiłem, i które wyjaśnia zachowanie, które widzi. – EJP

+1

Czy zamknięcie strumienia wyjściowego również nie spowoduje, że wywołanie metody getInputStream nie będzie konieczne? –

2

Jak moje eksperymenty wykazały (java 1.7.0_01) kod:

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

nie wysyła nic do serwera. Po prostu zapisuje to, co jest zapisane w buforze pamięci. Jeśli chcesz przesłać duży plik przez POST, musisz mieć pewność, że masz wystarczająco dużo pamięci. Na komputerze/serwerze może nie być tak dużym problemem, ale na Androidzie może to spowodować błąd braku pamięci. Oto przykład, jak wygląda śledzenie stosu podczas próby zapisu do strumienia wyjściowego, a pamięć kończy się.

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) 

Na dole śladu widać metodę makePOST() który wykonuje następujące czynności:

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

I writer.write() zgłasza wyjątek. Również moje eksperymenty pokazały, że każdy wyjątek związany z rzeczywistym połączeniem/IO z serwerem jest generowany tylko po wywołaniu urlCon.getOutputStream(). Nawet urlCon.connect() wydaje się być "sztuczną" metodą, która nie wykonuje żadnego fizycznego połączenia. Jeśli jednak wywołasz urlCon.getContentLengthLong(), który zwraca pole Content-Length: nagłówek z nagłówków odpowiedzi serwera, wówczas URLConnection.getOutputStream() zostanie wywołany automatycznie i na wypadek wyjątku - zostanie zgłoszony.

wyjątki rzucane przez urlCon.getOutputStream() są IOException, a ja spotkałem te 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); 
       } 

Mam nadzieję, że moja mała Badania pomaga ludziom, jak klasa URLConnection jest intuicyjne nieco w niektórych przypadkach w ten sposób, przy jej wdrażaniu - trzeba wiedzieć, z czym ma do czynienia. Drugi powód to: podczas pracy z serwerami - praca z serwerem może się nie udać z wielu powodów (połączenie, dns, zapora ogniowa, httpresponses, serwer nie może zaakceptować połączenia, serwer nie może przetworzyć żądania w odpowiednim czasie). Dlatego ważne jest, aby zrozumieć , w jaki sposób zgłaszane wyjątki mogą wyjaśniać, co faktycznie dzieje się z połączeniem.

3

Chociaż metoda getInputStream() z pewnością może spowodować, że obiekt URLConnection zainicjuje żądanie HTTP, nie jest to wymagane.

Rozważmy rzeczywisty przepływ pracy:

  1. zbudować żądania
  2. Prześlij
  3. Process odpowiedź

Krok 1 obejmuje możliwość włączenia danych we wniosku, w drodze Encja HTTP. Tak się składa, że ​​klasa URLConnection dostarcza obiekt OutputStream jako mechanizm dostarczania tych danych (i słusznie z wielu powodów, które nie są tutaj szczególnie istotne). Wystarczy powiedzieć, że strumieniowa natura tego mechanizmu zapewnia programistom pewną elastyczność podczas dostarczania danych, w tym możliwość zamknięcia strumienia wyjściowego (i wszelkich strumieni wejściowych, które go zasilają), przed zakończeniem żądania.

Innymi słowy, krok 1 pozwala na dostarczenie jednostki danych dla żądania, a następnie kontynuowanie jej budowy (na przykład poprzez dodanie nagłówków).

Krok 2 to naprawdę wirtualny krok i może być zautomatyzowany (jak to jest w klasie URLConnection), ponieważ przesłanie żądania jest bez znaczenia bez odpowiedzi (przynajmniej w granicach protokołu HTTP).

Co prowadzi nas do kroku 3. Podczas przetwarzania odpowiedzi HTTP, jednostka odpowiedzi - pobierana przez wywołanie getInputSteam() - jest tylko jedną z rzeczy, którymi moglibyśmy być zainteresowani. Odpowiedź składa się ze statusu, nagłówków i opcjonalnie podmiot. Po raz pierwszy jeden z nich jest wymagany, URLConnection wykona wirtualny krok 2 i złożyć wniosek.

Nie ma znaczenia, czy jednostka jest wysyłana przez strumień wyjściowy połączenia, czy też nie, i bez względu na to, czy jednostka odpowiedzi jest oczekiwana z powrotem, program ZAWSZE chce znać wynik (zgodnie z kodem statusu HTTP). Wywołanie getResponseCode() na URLConnection zapewnia ten status, a włączenie wyniku może zakończyć konwersację HTTP bez wywoływania metody getInputStream().

Tak więc, jeśli dane są złożone i żywa reakcja nie ma, nie rób tego:

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

... to zrobić:

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