为什么必须调用 URLConnection#getInputStream 才能写入 URLConnection#getOutputStream?

发布于 2024-10-15 03:55:39 字数 2893 浏览 3 评论 0原文

我正在尝试写入 URLConnection#getOutputStream,但是,在我调用 URLConnection#getInputStream。即使我设置 URLConnection# doInput 设置为 false,仍然不会发送。有谁知道这是为什么? API 文档中没有对此进行描述。

有关 URLConnection 的 Java API 文档: 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();
            }
        }

    }

}

I'm trying to write out to URLConnection#getOutputStream, however, no data is actually sent until I call URLConnection#getInputStream. Even if I set URLConnnection#doInput to false, it still will not send. Does anyone know why this is? There's nothing in the API documentation that describes this.

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

Java's Tutorial on Reading from and Writing to a 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();
            }
        }

    }

}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

¢蛋碎的人ぎ生 2024-10-22 03:55:39

URLConnection 和 HttpURLConnection 的 API(无论好坏)旨在让用户遵循非常特定的事件序列:

  1. 设置请求属性
  2. (可选) getOutputStream()、写入流、关闭流
  3. getInputStream()、读取流,关闭流

如果您的请求是 POST 或 PUT,则需要可选的步骤 #2。

据我所知,OutputStream 不像套接字,它不直接连接到服务器上的InputStream。相反,在关闭或刷新流并调用 getInputStream() 后,您的输出将构建到请求中并发送。语义基于您想要阅读响应的假设。我见过的每个例子都显示了这个事件的顺序。我当然同意您和其他人的观点,即与普通的流 I/O API 相比,此 API 是违反直觉的。

您链接到的 教程 指出“URLConnection 是一个 HTTP以班级为中心”。我将其解释为这些方法是围绕请求-响应模型设计的,并假设它们将如何使用。

无论如何,我发现这个错误报告解释了预期的操作该类比 javadoc 文档更好。该报告的评估指出“发送请求的唯一方法是调用 getInputStream”。

The API for URLConnection and HttpURLConnection are (for better or worse) designed for the user to follow a very specific sequence of events:

  1. Set Request Properties
  2. (Optional) getOutputStream(), write to the stream, close the stream
  3. getInputStream(), read from the stream, close the stream

If your request is a POST or PUT, you need the optional step #2.

To the best of my knowledge, the OutputStream is not like a socket, it is not directly connected to an InputStream on the server. Instead, after you close or flush the stream, AND call getInputStream(), your output is built into a Request and sent. The semantics are based on the assumption that you will want to read the response. Every example that I've seen shows this order of events. I would certainly agree with you and others that this API is counterintuitive when compared to the normal stream I/O API.

The tutorial you link to states that "URLConnection is an HTTP-centric class". I interpret that to mean that the methods are designed around a Request-Response model, and make the assumption that is how they will be used.

For what it's worth, I found this bug report that explains the intended operation of the class better than the javadoc documentation. The evaluation of the report states "The only way to send out the request is by calling getInputStream."

如梦 2024-10-22 03:55:39

尽管 getInputStream() 方法当然可以使 URLConnection 对象发起 HTTP 请求,但这并不是必需的。

考虑实际的工作流程:

  1. 构建请求
  2. 提交
  3. 处理响应

第 1 步包括通过 HTTP 实体在请求中包含数据的可能性。碰巧 URLConnection 类提供了一个 OutputStream 对象作为提供此数据的机制(由于许多与此处不特别相关的原因,这是理所当然的)。可以说,这种机制的流式传输特性为程序员在提供数据时提供了一定的灵活性,包括在完成请求之前关闭输出流(以及为其提供数据的任何输入流)的能力。

换句话说,步骤 1 允许为请求提供数据实体,然后继续构建它(例如通过添加标头)。

步骤 2 实际上是一个虚拟步骤,并且可以自动化(就像在 URLConnection 类中一样),因为如果没有响应,提交请求就没有意义(至少在 HTTP 协议的范围内)。

这将我们带到了步骤 3。处理 HTTP 响应时,响应实体(通过调用 getInputSteam() 检索)只是我们可能感兴趣的事情之一。响应由状态、标头和可选的实体。第一次请求其中任何一个时,URLConnection 将执行虚拟步骤 2 并提交请求。

无论是否通过连接的输出流发送实体,也无论是否期望返回响应实体,程序总是想知道结果(由 HTTP 状态代码提供)。在 URLConnection 上调用 getResponseCode() 可提供此状态,并且打开结果可能会结束 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

Although the getInputStream() method can certainly cause a URLConnection object to initiate an HTTP request, it is not a requirement to do so.

Consider the actual workflow:

  1. Build a request
  2. Submit
  3. Process the response

Step 1 includes the possibility of including data in the request, by way of an HTTP entity. It just so happens that the URLConnection class provides an OutputStream object as the mechanism for providing this data (and rightfully so for many reasons that aren't particularly relevant here). Suffice to say that the streaming nature of this mechanism provides the programmer an amount of flexibility when supplying the data, including the ability to close the output stream (and any input streams feeding it), before finishing the request.

In other words, step 1 allows for supplying a data entity for the request, then continuing to build it (such as by adding headers).

Step 2 is really a virtual step, and can be automated (like it is in the URLConnection class), since submitting a request is meaningless without a response (at least within the confines of the HTTP protocol).

Which brings us to Step 3. When processing an HTTP response, the response entity -- retrieved by calling getInputSteam() -- is just one of the things we might be interested in. A response consists of a status, headers, and optionally an entity. The first time any one of these is requested, the URLConnection will perform virtual step 2 and submit the request.

No matter if an entity is being sent via the connection's output stream or not, and no matter whether a response entity is expected back, a program will ALWAYS want to know the result (as provided by the HTTP status code). Calling getResponseCode() on the URLConnection provides this status, and switching on the result may end the HTTP conversation without ever calling getInputStream().

So, if data is being submitted, and a response entity is not expected, don't do this:

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

... do this:

// request is now built, so...
int result = urlConnection.getResponseCode();
// act based on this result
星星的轨迹 2024-10-22 03:55:39

正如我的实验所示(java 1.7.0_01)代码:

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

不向服务器发送任何内容。它只是将写入的内容保存到内存缓冲区中。因此,如果您要通过 POST 上传大文件,您需要确保有足够的内存。在桌面/服务器上这可能不是一个大问题,但在 Android 上可能会导致内存不足错误。以下示例展示了尝试写入输出流且内存耗尽时堆栈跟踪的外观。

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() 后,才会引发与服务器的实际连接/IO 相关的任何异常。甚至 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、防火墙、http响应、服务器无法接受连接、服务器无法及时处理请求),与服务器的工作可能会失败。因此,了解引发的异常如何解释连接实际发生的情况非常重要。

As my experiments have shown (java 1.7.0_01) the code:

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

Doesn't send anything to the server. It just saves what's written there to the memory buffer. Thus in case you're going to upload a large file via POST - you need to be sure that you have enough memory. On desktop/server it may not be such a big problem, but on android that may result in out of memory error. Here's the example of how the stack trace looks when trying to write to output stream, and memory runs out.

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)

On the bottom of the trace you can see the makePOST() method which does the following:

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

And writer.write() throws the exception.
Also my experiments have shown that any exception related to the actual connection/IO with the server is thrown only after urlCon.getOutputStream() is called. Even urlCon.connect() seems to be "dummy" method which doesn't do any physical connection.
However if you call urlCon.getContentLengthLong() which returns Content-Length: header field from the server response-headers - then URLConnection.getOutputStream() will be called automatically and in case there's exception - it will be thrown.

The exceptions thrown by urlCon.getOutputStream() are all IOException, and I have met the follwing ones:

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

Hopefully my little research helps to people, as URLConnection class is a bit counter-intuitive in some cases thus, when implementing it - one needs to know what's it deals with.

Second reason is: when working with servers - the work with server may fail because of many reasons (connection, dns, firewall, httpresponses, server not being able to accept connection, server not being able to process request timely). Thus it is important to understand how exceptions raised can explain about what's actually happening with the connection.

哀由 2024-10-22 03:55:39

调用 getInputStream() 会发出信号,表明客户端已完成发送请求,并准备好接收响应(根据 HTTP 规范)。看来 URLConnection 类内置了这个概念,并且当请求输入流时必须刷新()输出流。

正如其他响应者所指出的,您应该能够自己调用flush()来触发写入。

Calling getInputStream() signals that the client is finished sending it's request, and is ready to receive the response (per HTTP spec). It seems that the URLConnection class has this notion built into it, and must be flush()ing the output stream when the input stream is asked for.

As the other responder noted, you should be able to call flush() yourself to trigger the write.

巴黎盛开的樱花 2024-10-22 03:55:39

根本原因是它必须自动计算内容长度标头(除非您使用分块或流模式)。在看到所有输出之前它无法执行此操作,并且必须在输出之前发送它,因此它必须缓冲输出。并且需要一个决定性的事件来知道最后的输出何时实际被写入。因此它使用 getInputStream() 来实现。此时,它会写入包括内容长度的标头,然后是输出,然后开始读取输入。

The fundamental reason is that it has to compute a Content-length header automatically (unless you are using chunked or streaming mode). It can't do that until it has seen all the output, and it has to send it before the output, so it has to buffer the output. And it needs a decisive event to know when the last output has actually been written. So it uses getInputStream() for that. At that time it writes the headers including the content-length, then the output, then it starts reading the input.

倒数 2024-10-22 03:55:39

(从你的第一个问题重新发布。无耻的自插)
不要自己摆弄 URLConnection,让 Resty 来处理它。

这是您需要编写的代码(我假设您正在收到文本):

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

(Repost from your first question. Shameless self-plug)
Don't fiddle around with URLConnection yourself, let Resty handle it.

Here's the code you would need to write (I assume you are getting text back):

import static us.monoid.web.Resty.*;
import us.monoid.web.Resty;  
...    
new Resty().text(TEST_URL, content("HELLO WORLD")).toString();
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文