Почему вы должны вызывать URLConnection # getInputStream, чтобы иметь возможность писать в URLConnection # getOutputStream? - PullRequest
30 голосов
/ 30 января 2011

Я пытаюсь записать в URLConnection#getOutputStream, однако никакие данные на самом деле не отправляются, пока я не позвоню URLConnection#getInputStream. Даже если для URLConnnection#doInput установлено значение false, оно все равно не будет отправлено. Кто-нибудь знает, почему это? Нет ничего в документации API, которая описывает это.

Документация по API Java для URLConnection: http://download.oracle.com/javase/6/docs/api/java/net/URLConnection.html

Учебник Java по чтению и записи в 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();
            }
        }

    }

}

Ответы [ 6 ]

38 голосов
/ 30 января 2011

API для URLConnection и HttpURLConnection (к лучшему или к худшему) предназначены для пользователя, чтобы следовать очень определенной последовательности событий:

  1. Установить свойства запроса
  2. (Необязательно)getOutputStream (), запись в поток, закрытие потока
  3. getInputStream (), чтение из потока, закрытие потока

Если ваш запрос POST или PUT, вам нужнонеобязательный шаг # 2.

Насколько мне известно, OutputStream не похож на сокет, он напрямую не связан с InputStream на сервере.Вместо этого после закрытия или очистки потока и вызова getInputStream () ваш вывод встроен в запрос и отправляется.Семантика основана на предположении, что вы захотите прочитать ответ.Каждый пример, который я видел, показывает этот порядок событий.Я, безусловно, согласен с вами и другими, что этот API нелогичен по сравнению с обычным API потокового ввода-вывода.

Учебное пособие , на которое вы ссылаетесь, гласит, что "URLConnection является HTTP-ориентированнымучебный класс".Я интерпретирую это как означающее, что методы разработаны на основе модели «запрос-ответ», и делаю предположение, что именно так они и будут использоваться.

Для чего это стоит, я нашел этот отчет об ошибках это объясняет предполагаемую работу класса лучше, чем документация javadoc.Оценка отчета гласит: «Единственный способ отправить запрос - вызвать getInputStream.»

4 голосов
/ 12 декабря 2012

Хотя метод getInputStream (), безусловно, может вызывать объект URLConnection для инициирования HTTP-запроса, это не является обязательным требованием.

Рассмотрим фактический рабочий процесс:

  1. Построить запрос
  2. Отправить
  3. Обработка ответа

Шаг 1 включает в себя возможность включения данных в запрос посредством объекта HTTP.Просто так получилось, что класс URLConnection предоставляет объект OutputStream в качестве механизма для предоставления этих данных (и это справедливо по многим причинам, которые здесь не особенно актуальны).Достаточно сказать, что потоковая природа этого механизма предоставляет программисту определенную гибкость при подаче данных, включая возможность закрыть выходной поток (и любые входные потоки, подающие его) перед завершением запроса.

Другими словами, шаг 1 позволяет предоставить объект данных для запроса, а затем продолжить его создание (например, путем добавления заголовков).

Шаг 2 на самом деле является виртуальным шагом и может быть автоматизирован (например, так:находится в классе URLConnection), поскольку отправка запроса не имеет смысла без ответа (по крайней мере, в пределах протокола HTTP).

, что приводит нас к шагу 3. При обработке ответа HTTP объект ответа- извлекается путем вызова getInputSteam () - это только одна из вещей, которые могут нас заинтересовать. Ответ состоит из статуса, заголовков и, возможно, сущности.При первом запросе любого из них URLConnection выполнит виртуальный шаг 2 и отправит запрос.

Независимо от того, отправляется ли объект через выходной поток соединения или нет, и независимо от того, был ли ответобъект ожидается обратно, программа ВСЕГДА захочет узнать результат (как предусмотрено кодом состояния HTTP).Вызов getResponseCode () в URLConnection обеспечивает этот статус, и включение результата может завершить HTTP-разговор, даже не вызывая getInputStream ().

Итак, если данные отправляются, а объект ответа не ожидается,не делай этого:

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

... делай это:

// request is now built, so...
int result = urlConnection.getResponseCode();
// act based on this result
2 голосов
/ 09 мая 2012

Как показали мои эксперименты (java 1.7.0_01), код:

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

Не отправляет ничего на сервер.Он просто сохраняет то, что там записано, в буфер памяти.Таким образом, если вы собираетесь загружать большой файл через POST - вы должны быть уверены, что у вас достаточно памяти.На десктопе / сервере это может быть не такой большой проблемой, но на андроиде это может привести к нехватке памяти.Вот пример того, как выглядит трассировка стека при попытке записи в выходной поток, и память заканчивается.

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)

В нижней части трассы вы можете увидеть метод makePOST(), который выполняет следующее:

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

И writer.write() выдает исключение.Также мои эксперименты показали, что любое исключение, связанное с фактическим соединением / вводом-выводом с сервером, генерируется только после вызова urlCon.getOutputStream().Даже urlCon.connect() кажется «фиктивным» методом, который не имеет никакой физической связи.Однако, если вы вызываете urlCon.getContentLengthLong(), который возвращает поле заголовка Content-Length: из ответных заголовков сервера - тогда URLConnection.getOutputStream () будет вызываться автоматически, а в случае возникновения исключения - будет выброшено.все исключения, выдаваемые urlCon.getOutputStream(), являются IOException, и я встретил следующие:

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

Надеюсь, мое небольшое исследование поможет людям, так как класс URLConnection в некоторых случаях немного нелогичный, поэтомуРеализация - нужно знать, с чем это имеет дело.

Вторая причина: при работе с серверами - работа с сервером может быть неудачной по многим причинам (соединение, DNS, межсетевой экран, httpresponses, сервер не может принять соединение, сервер не может своевременно обработать запрос).Таким образом, важно понять , как возникшие исключения могут объяснить, что в действительности происходит с соединением .

1 голос
/ 31 января 2011

Фундаментальная причина заключается в том, что он должен автоматически вычислять заголовок длины содержимого (если только вы не используете чанкованный или потоковый режим).Он не может сделать это до тех пор, пока не увидит все выходные данные, и он должен отправить их до вывода, поэтому он должен буферизовать выходные данные.И нужно решающее событие, чтобы знать, когда последний вывод был действительно записан.Так что для этого он использует getInputStream ().В это время он записывает заголовки, включая длину содержимого, затем вывод и затем начинает читать ввод.

1 голос
/ 30 января 2011

Вызов getInputStream () сигнализирует о том, что клиент завершил отправку своего запроса и готов получить ответ (согласно спецификации HTTP).Кажется, что класс URLConnection имеет это встроенное понятие и должен быть flush () для выходного потока, когда запрашивается входной поток.

Как заметил другой респондент, вы должны иметь возможность вызывать flush() самостоятельно, чтобы вызвать запись.

0 голосов
/ 30 января 2011

(Репост от вашего первого вопроса. Бесстыдная самостоятельная вилка) Не возитесь с URLConnection сами, пусть Resty справится с этим.

Вот код, который вам нужно написать (я полагаю, вы получаете текст обратно):

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