Czy możesz wyjaśnić proces łączenia się z HttpURLConnection?


134

Używam HTTPURLConnectiondo łączenia się z usługą internetową. Wiem, jak używać, HTTPURLConnectionale chcę zrozumieć, jak to działa. Zasadniczo chcę wiedzieć, co następuje:

  • W którym momencie HTTPURLConnectionpróbuje nawiązać połączenie z podanym adresem URL?
  • W którym momencie mogę wiedzieć, że udało mi się pomyślnie nawiązać połączenie?
  • Czy nawiązanie połączenia i wysłanie rzeczywistego żądania odbywa się w jednym wywołaniu kroku / metody? Jaka to metoda?
  • Czy możesz wyjaśnić funkcję getOutputStreami getInputStreamużywając terminu laika? Zauważyłem, że gdy serwer, z którym próbuję się połączyć, nie działa, otrzymuję sygnał Exceptionat getOutputStream. Czy to oznacza, że ​​połączenie HTTPURLConnectionzacznie się dopiero wtedy, gdy wywołam getOutputStream? A co powiesz na getInputStream? Ponieważ jestem w stanie uzyskać odpowiedź tylko pod adresem getInputStream, czy to oznacza, że ​​nie wysłałem jeszcze żadnego żądania, getOutputStreamale po prostu nawiązałem połączenie? Czy HttpURLConnectionwrócić do serwera, aby zażądać odpowiedzi, gdy wywołuję getInputStream?
  • Czy mam rację, mówiąc, że openConnectionpo prostu tworzy nowy obiekt połączenia, ale jeszcze nie ustanawia żadnego połączenia?
  • Jak mogę zmierzyć narzut odczytu i podłączyć narzut?

Odpowiedzi:


184
String message = URLEncoder.encode("my message", "UTF-8");

try {
    // instantiate the URL object with the target URL of the resource to
    // request
    URL url = new URL("http://www.example.com/comment");

    // instantiate the HttpURLConnection with the URL object - A new
    // connection is opened every time by calling the openConnection
    // method of the protocol handler for this URL.
    // 1. This is the point where the connection is opened.
    HttpURLConnection connection = (HttpURLConnection) url
            .openConnection();
    // set connection output to true
    connection.setDoOutput(true);
    // instead of a GET, we're going to send using method="POST"
    connection.setRequestMethod("POST");

    // instantiate OutputStreamWriter using the output stream, returned
    // from getOutputStream, that writes to this connection.
    // 2. This is the point where you'll know if the connection was
    // successfully established. If an I/O error occurs while creating
    // the output stream, you'll see an IOException.
    OutputStreamWriter writer = new OutputStreamWriter(
            connection.getOutputStream());

    // write data to the connection. This is data that you are sending
    // to the server
    // 3. No. Sending the data is conducted here. We established the
    // connection with getOutputStream
    writer.write("message=" + message);

    // Closes this output stream and releases any system resources
    // associated with this stream. At this point, we've sent all the
    // data. Only the outputStream is closed at this point, not the
    // actual connection
    writer.close();
    // if there is a response code AND that response code is 200 OK, do
    // stuff in the first if block
    if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
        // OK

        // otherwise, if any other status code is returned, or no status
        // code is returned, do stuff in the else block
    } else {
        // Server returned HTTP error code.
    }
} catch (MalformedURLException e) {
    // ...
} catch (IOException e) {
    // ...
}

Pierwsze 3 odpowiedzi na Twoje pytania są wymienione jako komentarze w tekście obok każdej metody w powyższym przykładzie HTTP POST.

Z getOutputStream :

Zwraca strumień wyjściowy, który zapisuje w tym połączeniu.

Zasadniczo myślę, że dobrze rozumiesz, jak to działa, więc pozwolę sobie powtórzyć w kategoriach laika. getOutputStreamw zasadzie otwiera strumień połączenia z zamiarem zapisania danych na serwerze. W powyższym przykładzie "wiadomość" może być komentarzem, który wysyłamy do serwera, który reprezentuje komentarz pozostawiony w poście. Kiedy widzisz getOutputStream, otwierasz strumień połączenia do zapisu, ale tak naprawdę nie zapisujesz żadnych danych, dopóki nie zadzwonisz writer.write("message=" + message);.

Z getInputStream () :

Zwraca strumień wejściowy, który odczytuje z tego otwartego połączenia. SocketTimeoutException może zostać zgłoszony podczas odczytu ze zwróconego strumienia wejściowego, jeśli limit czasu odczytu wygaśnie, zanim dane będą dostępne do odczytu.

getInputStreamrobi odwrotnie. Podobnie getOutputStream, otwiera również strumień połączenia , ale jego celem jest odczyt danych z serwera, a nie zapisywanie do niego. Jeśli połączenie lub otwarcie strumienia nie powiedzie się, zobaczysz plik SocketTimeoutException.

A co z getInputStream? Ponieważ jestem w stanie uzyskać odpowiedź tylko w getInputStream, czy to oznacza, że ​​nie wysłałem jeszcze żadnego żądania do getOutputStream, ale po prostu nawiązałem połączenie?

Pamiętaj, że wysłanie żądania i wysłanie danych to dwie różne operacje. Kiedy wywołujesz getOutputStream lub getInputStream url.openConnection() , wysyłasz żądanie do serwera w celu ustanowienia połączenia. Występuje uzgadnianie, które ma miejsce, gdy serwer odsyła potwierdzenie, że połączenie zostało nawiązane. W tym momencie jesteś przygotowany do wysyłania lub odbierania danych. Dlatego nie musisz wywoływać funkcji getOutputStream, aby ustanowić połączenie i otworzyć strumień, chyba że celem żądania jest wysłanie danych.

Mówiąc prościej, złożenie getInputStreamprośby jest równoznaczne z wykonaniem telefonu do domu przyjaciela, aby powiedzieć: „Hej, czy jest w porządku, jeśli przyjdę i pożyczę tę parę uchwytów imadła?”. a twój przyjaciel ustanawia uścisk dłoni mówiąc: „Jasne! Chodź i weź to”. Następnie, w tym momencie, zostaje nawiązane połączenie, idziesz do domu przyjaciela, pukasz do drzwi, prosisz o uchwyty imadła i wracasz do domu.

Skorzystanie z podobnego przykładu dla getOutputStreamobejmowałoby zadzwonienie do znajomego i powiedzenie: „Hej, mam pieniądze, które jestem ci winien, czy mogę ci je wysłać”? Twój przyjaciel, potrzebujący pieniędzy i chory w środku, że trzymałeś je tak długo, mówi: „Jasne, daj spokój tani draniu”. Więc idziesz do domu swojego przyjaciela i "PRZEŚLIJ" mu pieniądze. Potem cię wyrzuca i wracasz do domu.

Kontynuując przykład laika, spójrzmy na niektóre Wyjątki. Jeśli zadzwoniłeś do znajomego i nie było go w domu, może to oznaczać błąd 500. Jeśli zadzwoniłeś i otrzymałeś wiadomość o rozłączonym numerze, ponieważ twój znajomy jest zmęczony ciągłym pożyczaniem pieniędzy, oznacza to, że nie znaleziono strony 404. Jeśli telefon nie działa, ponieważ nie zapłaciłeś rachunku, może to być wyjątek IOException. (UWAGA: ta sekcja może nie być w 100% poprawna. Ma na celu dać ogólne pojęcie o tym, co się dzieje w kategoriach laika).

Pytanie nr 5:

Tak, masz rację, że openConnection po prostu tworzy nowy obiekt połączenia, ale go nie ustanawia. Połączenie jest nawiązywane w momencie wywołania metody getInputStream lub getOutputStream.

openConnectiontworzy nowy obiekt połączenia. Z adresu URL.openConnection javadocs :

Nowe połączenie jest otwierane za każdym razem przez wywołanie metody openConnection programu obsługi protokołu dla tego adresu URL.

Połączenie jest ustanawiane, gdy wywołujesz openConnection, a InputStream, OutputStream lub oba są wywoływane podczas ich tworzenia.

Pytanie nr 6 :

Aby zmierzyć narzut, zwykle zawijam bardzo prosty kod czasowy wokół całego bloku połączeń, na przykład:

long start = System.currentTimeMillis();
log.info("Time so far = " + new Long(System.currentTimeMillis() - start) );

// run the above example code here
log.info("Total time to send/receive data = " + new Long(System.currentTimeMillis() - start) );

Jestem pewien, że istnieją bardziej zaawansowane metody pomiaru czasu żądania i narzutu, ale ogólnie jest to wystarczające dla moich potrzeb.

Aby uzyskać informacje na temat zamykania połączeń, o które nie pytałeś, zobacz Kiedy w Javie zamyka się połączenie URL? .


Cześć. Dzięki!!! To było rzeczywiście szczegółowe wyjaśnienie i naprawdę doceniam twoją odpowiedź. Jeśli dobrze rozumiem twoją odpowiedź, zarówno getOutputStream, jak i getInputStream ustanawiają połączenie, jeśli nie zostało jeszcze nawiązane połączenie. Jeśli wywołam getOutputStream, a następnie wywołam getInputStream, wewnętrznie, HTTPURLConnection nie będzie już przywracać połączenia z getInputStream, ponieważ udało mi się już ustanowić je w getOutStream? HttpURLConnection wykorzysta ponownie każde połączenie, które udało mi się ustanowić w getOutputStream w getInputStream.
Arci,

Cont .: A może ustanawia nowe i oddzielne połączenie dla getOutputStream i getInputStream? Ponadto, jeśli chcę uzyskać narzut połączenia, właściwym miejscem do umieszczenia licznika czasu jest przed i po getOutputStream. Jeśli chcę uzyskać narzut odczytu, właściwym miejscem do umieszczenia licznika czasu jest przed i po getInputStream.
Arci,

Pamiętaj, co mówi javadoc o getInputStream i getOutputStream: Returns an output stream that writes to this connection.i Returns an input stream that reads from this open connection.. Strumień wyjściowy i wejściowy są oddzielone od połączenia.
jmort253

8
Warto zauważyć, że wydaje się, że obiekt HttpURLConnection dociera do docelowego adresu URL tylko w punkcie, w którym MUSI to zrobić. W twoim przykładzie masz strumienie wejściowe i wyjściowe, które oczywiście nie mogą nic zrobić, dopóki połączenie nie zostanie otwarte. Dużo prostszym przypadkiem jest operacja GET, w której nic nie robisz, tylko inicjujesz połączenie, a następnie sprawdzasz kod odpowiedzi. W takim przypadku połączenie nie jest w rzeczywistości nawiązywane do momentu wywołania metody getResponseCode (). W przeciwnym razie jest to świetne wyjaśnienie i eksploracja cyklu życia połączenia!
Spanky Quigman

1
Wcześniej byłem zdezorientowany między wystąpieniem „UrlConnection” a podstawowym połączeniem Tcp / Ip / SSL, 2 oddzielne koncepcje. To pierwsze jest w zasadzie synonimem pojedynczego żądania strony HTTP. To ostatnie jest czymś, co, miejmy nadzieję, zostanie utworzone tylko raz, jeśli wykonujesz wiele żądań stron na tym samym serwerze.
Tim Cooper,


1

W którym momencie protokół HTTPURLConnection próbuje nawiązać połączenie z podanym adresem URL?

Na porcie określonym w adresie URL, jeśli istnieje, w przeciwnym razie 80 dla protokołu HTTP i 443 dla protokołu HTTPS. Uważam, że jest to udokumentowane.

W którym momencie mogę wiedzieć, że udało mi się pomyślnie nawiązać połączenie?

Gdy wywołujesz metodę getInputStream (), getOutputStream () lub getResponseCode () bez uzyskania wyjątku.

Czy nawiązanie połączenia i wysłanie rzeczywistego żądania odbywa się w jednym wywołaniu kroku / metody? Jaka to metoda?

Nie i nic.

Czy możesz wyjaśnić funkcję getOutputStream i getInputStream w języku laika?

Każde z nich najpierw łączy się, jeśli to konieczne, a następnie zwraca wymagany strumień.

Zauważyłem, że gdy serwer, z którym próbuję się połączyć, jest wyłączony, w getOutputStream pojawia się wyjątek. Czy to oznacza, że ​​HTTPURLConnection zacznie nawiązywać połączenie tylko wtedy, gdy wywołam getOutputStream? A co z getInputStream? Ponieważ jestem w stanie uzyskać odpowiedź tylko w getInputStream, czy to oznacza, że ​​nie wysłałem jeszcze żadnego żądania do getOutputStream, ale po prostu nawiązałem połączenie? Czy HttpURLConnection wraca do serwera, aby zażądać odpowiedzi, gdy wywołuję getInputStream?

Patrz wyżej.

Czy mam rację, mówiąc, że openConnection po prostu tworzy nowy obiekt połączenia, ale jeszcze nie ustanawia żadnego połączenia?

Tak.

Jak mogę zmierzyć narzut odczytu i podłączyć narzut?

Połącz: nie spiesz się, aby funkcja getInoutStream () lub getOutputStream () została zwrócona, w zależności od tego, które wywołanie jest pierwsze. Czytaj: czas od rozpoczęcia pierwszego czytania do uzyskania EOS.


1
Myślę, że OP oznaczało, w którym punkcie jest ustanawiane połączenie iw którym momencie możemy poznać stan połączenia. Adres URL portu nie łączy się z. Domyślam się, że było to skierowane do openConnection () i getInoutStream () / getOutputStream () / getResponseCode (), na które odpowiedź jest później.
Aniket Thakur,

1

W którym momencie protokół HTTPURLConnection próbuje nawiązać połączenie z podanym adresem URL?

Warto wyjaśnić, istnieje instancja „UrlConnection”, a następnie podstawowe połączenie gniazda Tcp / Ip / SSL , 2 różne koncepcje. Instancja „UrlConnection” lub „HttpUrlConnection” jest synonimem pojedynczego żądania strony HTTP i jest tworzona podczas wywoływania url.openConnection (). Ale jeśli zrobisz wiele adresów url.openConnection () z jednej instancji 'url', to jeśli będziesz mieć szczęście, ponownie użyją tego samego gniazda Tcp / Ip i uzgadniania SSL ... co jest dobre, jeśli jesteś wykonywanie wielu żądań stron do tego samego serwera, szczególnie przydatne, jeśli używasz SSL, gdzie narzut związany z ustanowieniem gniazda jest bardzo wysoki.

Zobacz: implementacja HttpURLConnection


0

Wykonałem ćwiczenie, aby przechwycić wymianę pakietów na niskim poziomie i stwierdziłem, że połączenie sieciowe jest wyzwalane tylko przez operacje takie jak getInputStream, getOutputStream, getResponseCode, getResponseMessage itp.

Oto wymiana pakietów przechwycona, gdy próbuję napisać mały program do przesłania pliku do Dropbox.

wprowadź opis obrazu tutaj

Poniżej znajduje się mój program zabawek i adnotacja

    /* Create a connection LOCAL object,
     * the openConnection() function DOES NOT initiate
     * any packet exchange with the remote server.
     * 
     * The configurations only setup the LOCAL
     * connection object properties.
     */
    HttpURLConnection connection = (HttpURLConnection) dst.openConnection();
    connection.setDoOutput(true);
    connection.setRequestMethod("POST");
    ...//headers setup
    byte[] testContent = {0x32, 0x32};

    /**
     * This triggers packet exchange with the remote
     * server to create a link. But writing/flushing
     * to a output stream does not send out any data.
     * 
     * Payload are buffered locally.
     */
    try (BufferedOutputStream outputStream = new BufferedOutputStream(connection.getOutputStream())) {
        outputStream.write(testContent);
        outputStream.flush();
    }

    /**
     * Trigger payload sending to the server.
     * Client get ALL responses (including response code,
     * message, and content payload) 
     */
    int responseCode = connection.getResponseCode();
    System.out.println(responseCode);

    /* Here no further exchange happens with remote server, since
     * the input stream content has already been buffered
     * in previous step
     */
    try (InputStream is = connection.getInputStream()) {
        Scanner scanner = new Scanner(is);
        StringBuilder stringBuilder = new StringBuilder();
        while (scanner.hasNextLine()) {
        stringBuilder.append(scanner.nextLine()).append(System.lineSeparator());
        }
    }

    /**
     * Trigger the disconnection from the server.
     */
    String responsemsg = connection.getResponseMessage();
    System.out.println(responsemsg);
    connection.disconnect();
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.