我正在尝试写出URLConnection#getOutputStream
,但是,在我致电URLConnection#getInputStream
之前,实际上并未发送任何数据。即使我将URLConnnection#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();
}
}
}
}
答案 0 :(得分:37)
URLConnection和HttpURLConnection的API(无论好坏)是为用户设计的,以便遵循非常特定的事件序列:
如果您的请求是POST或PUT,则需要选择步骤#2。
据我所知,OutputStream不像套接字,它不直接连接到服务器上的InputStream。相反,在关闭或刷新流之后,AND调用getInputStream(),您的输出将内置到Request中并发送。语义基于您希望读取响应的假设。我见过的每个例子都显示了这种事件的顺序。我肯定会同意你和其他人的说法,与普通的流I / O API相比,这个API是违反直觉的。
您链接到的tutorial表示“URLConnection是一个以HTTP为中心的类”。我认为这意味着这些方法是围绕Request-Response模型设计的,并假设它们将如何使用。
对于它的价值,我发现这个bug report比javadoc文档更好地解释了该类的预期操作。对报告的评估指出“发出请求的唯一方法是调用getInputStream。”
答案 1 :(得分:4)
尽管getInputStream()方法肯定会导致URLConnection对象发起HTTP请求,但并不要求这样做。
考虑实际的工作流程:
步骤1包括通过HTTP实体在请求中包括数据的可能性。碰巧的是,URLConnection类提供了一个OutputStream对象作为提供这些数据的机制(并且由于许多原因在这里并不特别相关)。可以说,在提供数据之前,这种机制的流媒体特性为程序员提供了一定的灵活性,包括在完成请求之前关闭输出流(以及任何输入流的输入流)的能力。
换句话说,步骤1允许为请求提供数据实体,然后继续构建它(例如通过添加标题)。
第2步实际上是一个虚拟步骤,可以自动化(就像在URLConnection类中一样),因为在没有响应的情况下提交请求是没有意义的(至少在HTTP协议的范围内)。
这将我们带到第3步。在处理HTTP响应时,通过调用getInputSteam()检索的响应实体只是我们可能感兴趣的事情之一。响应由状态,标题,以及可选的实体。第一次请求其中任何一个时,URLConnection将执行虚拟步骤2并提交请求。
无论是否通过连接的输出流发送实体,无论是否预期返回响应实体,程序始终都想知道结果(由HTTP状态代码提供)。在URLConnection上调用getResponseCode()会提供此状态,并且在不调用getInputStream()的情况下切换结果可能会结束HTTP会话。
因此,如果正在提交数据,并且不期望响应实体,请不要这样做:
// request is now built, so...
InputStream ignored = urlConnection.getInputStream();
......这样做:
// request is now built, so...
int result = urlConnection.getResponseCode();
// act based on this result
答案 2 :(得分:2)
正如我的实验所示(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()
似乎是“虚拟”方法,它不进行任何物理连接。
但是,如果从服务器response-headers中调用返回Content-Length:header字段的urlCon.getContentLengthLong()
,则会自动调用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响应,服务器无法接受连接,服务器无法及时处理请求),服务器的工作可能会失败。因此,了解如何引发的异常可以解释连接实际发生的情况非常重要。
答案 3 :(得分:1)
调用getInputStream()表示客户端已完成发送请求,并准备接收响应(根据HTTP规范)。似乎URLConnection类内置了这个概念,并且在请求输入流时必须刷新()输出流。
正如另一个响应者所说,你应该能够自己调用flush()来触发写入。
答案 4 :(得分:1)
根本原因是它必须自动计算内容长度标头(除非您使用的是分块或流模式)。在它看到所有输出之前它不能这样做,它必须在输出之前发送它,所以它必须缓冲输出。并且它需要一个决定性的事件来知道最后的输出何时被实际写入。所以它使用getInputStream()。那时它会写入包括内容长度的标题,然后是输出,然后它开始读取输入。
答案 5 :(得分:-3)
(从你的第一个问题转发。无耻的自我插件) 不要自己动摇URLConnection,让Resty处理它。
以下是您需要编写的代码(我假设您正在收回文本):
import static us.monoid.web.Resty.*;
import us.monoid.web.Resty;
...
new Resty().text(TEST_URL, content("HELLO WORLD")).toString();