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

我试图写出URLConnection#getOutputStream ,但是,没有数据实际上发送,直到我调用URLConnection#getInputStream 。 即使我将URLConnnection#doInput设置为false,它仍然不会发送。 有谁知道这是为什么? API文档中没有描述这一点。

有关URLConnection的Java API文档: http : //download.oracle.com/javase/6/docs/api/java/net/URLConnection.html

阅读和写入URLConnection的Java教程: 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(); } } } } 

用于URLConnection和HttpURLConnection的API(好或坏)是为用户devise的,以遵循一个非常特定的事件序列:

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

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

据我所知,OutputStream不像套接字,它不直接连接到服务器上的InputStream。 相反,在closures或刷新stream之后,AND调用getInputStream(),将输出内置到Request中并发送。 语义是基于你将要读取响应的假设。 我见过的每个例子都显示了这个事件的顺序。 我当然会同意你和其他人的看法,这个API与普通的streamI / O API相比是违反直觉的。

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

对于它的价值,我发现这个错误报告比javadoc文档更好地解释了类的预期操作。 报告的评估状态“发送请求的唯一方法是调用getInputStream”。

尽pipegetInputStream()方法肯定会导致URLConnection对象发起HTTP请求,但这不是必需的。

考虑实际的工作stream程:

  1. build立一个请求
  2. 提交
  3. 处理响应

步骤1包括通过HTTP实体在请求中包含数据的可能性。 只是偶然发现,URLConnection类提供了一个OutputStream对象作为提供这个数据的机制(正确地说,出于许多原因,这里并不特别相关)。 只要说这个机制的stream式性质,在提供数据时,程序员就能提供一定的灵活性,包括在完成请求之前closures输出stream(以及任何inputstream)。

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

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

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

无论是否通过连接的输出stream发送一个实体,也不pipe是否应该返回一个响应实体,程序总是希望知道结果(由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 

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

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

不发送任何东西到服务器。 它只是将写入的内容保存到内存缓冲区中。 因此,如果你打算通过POST上传一个大文件 – 你需要确保你有足够的内存。 在桌面/服务器上它可能不是一个这么大的问题,但在android上可能会导致内存不足的错误。 下面是当试图写入输出stream时堆栈跟踪的样子,内存用完了。

 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()抛出exception。 此外,我的实验显示,只有在urlCon.getOutputStream()之后, urlCon.getOutputStream()抛出与服务器的实际连接/ IO有关的任何exception。 即使urlCon.connect()似乎是“虚拟”的方法,不做任何物理连接。 但是,如果调用urlCon.getContentLengthLong() ,它将从服务器响应头返回Content-Length:头字段,那么URLConnection.getOutputStream()将自动调用,万一出现exception,将会抛出。

urlCon.getOutputStream()抛出的exception都是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响应,服务器不能接受连接,服务器无法及时处理请求)服务器的工作可能失败。 因此,了解如何引发exception可以解释连接实际发生的情况是非常重要

调用getInputStream()表示客户端完成发送请求,并准备接收响应(按照HTTP规范)。 看起来,URLConnection类具有内置的这个概念,并且在inputstream请求时必须flush()输出stream。

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

根本原因是它必须自动计算内容长度标题(除非您使用分块模式或stream模式)。 它不能这样做,直到它看到所有的输出,并且必须在输出之前发送它,所以它必须缓冲输出。 并且需要一个决定性的事件来知道最后的输出何时被写入。 所以它使用getInputStream()。 那时它会写入包含内容长度的标题,然后是输出,然后开始读取input。

(重写你的第一个问题,无耻的自我插件)不要自己动手URLConnection,让Resty处理它。

这里是你需要写的代码(我假设你正在获取文本):

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