Подтвердить что ты не робот

Почему вам нужно вызывать URLConnection # getInputStream, чтобы иметь возможность записывать URLConnection # getOutputStream?

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

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

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

    }

}
4b9b3361

Ответ 1

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

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

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

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

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

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

Ответ 2

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

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

  • Создайте запрос
  • Submit
  • Обработать ответ

Шаг 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

Ответ 3

Как показали мои эксперименты (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() выдает исключение. Также мои эксперименты показали, что любое исключение, связанное с фактическим соединением /IO с сервером, вызывается только после вызова urlCon.getOutputStream(). Даже urlCon.connect() представляется "dummy", который не выполняет никакого физического соединения. Однако, если вы вызываете 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, сервер не может принимать соединение, сервер не может своевременно обрабатывать запрос). Таким образом, важно понимать , как возникающие исключения могут объяснить, что на самом деле происходит с соединением.

Ответ 4

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

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

Ответ 5

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

Ответ 6

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

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

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