为什么你必须调用URLConnection#getInputStream才能写出到URLConnection#getOutputStream?

2022-09-01 09:48:33

我正在尝试写出URLConnection#getOutputStream,但是,在我调用URLConnection#getInputStream之前,实际上没有发送任何数据。即使我将URLConnnection#doInput设置为false,它仍然不会发送。有谁知道这是为什么?API 文档中没有任何内容对此进行了描述。

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

Java's Tutorial on Read from and Write 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();
            }
        }

    }

}

答案 1

URLConnection和HttpURLConnection的API(无论好坏)都是为用户设计的,以遵循非常特定的事件序列:

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

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

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

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

值得一提的是,我发现这个 bug 报告比 javadoc 文档更好地解释了该类的预期操作。该报告的评估指出“发送请求的唯一方法是调用getInputStream。


答案 2

尽管 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

推荐