假设我有一个java程序,它使用HTTP 1.1在服务器上发出HTTP请求,并且不关闭连接。我发出一个请求,并读取从绑定到套接字的输入流返回的所有数据。但是,在发出第二个请求时,我没有得到服务器的响应(或者流有问题 - 它不再提供任何输入)。如果我按顺序发出请求(请求,请求,读取)它工作正常,但(请求,读取,请求,读取)不会。
有人可以了解为什么会发生这种情况吗? (代码片段如下)。无论我做什么,第二个读取循环的isr_reader.read()只返回-1。
try{
connection = new Socket("SomeServer", port);
con_out = connection.getOutputStream();
con_in = connection.getInputStream();
PrintWriter out_writer = new PrintWriter(con_out, false);
out_writer.print("GET http://somesite HTTP/1.1\r\n");
out_writer.print("Host: thehost\r\n");
//out_writer.print("Content-Length: 0\r\n");
out_writer.print("\r\n");
out_writer.flush();
// If we were not interpreting this data as a character stream, we might need to adjust byte ordering here.
InputStreamReader isr_reader = new InputStreamReader(con_in);
char[] streamBuf = new char[8192];
int amountRead;
StringBuilder receivedData = new StringBuilder();
while((amountRead = isr_reader.read(streamBuf)) > 0){
receivedData.append(streamBuf, 0, amountRead);
}
// Response is processed here.
if(connection != null && !connection.isClosed()){
//System.out.println("Connection Still Open...");
out_writer.print("GET http://someSite2\r\n");
out_writer.print("Host: somehost\r\n");
out_writer.print("Connection: close\r\n");
out_writer.print("\r\n");
out_writer.flush();
streamBuf = new char[8192];
amountRead = 0;
receivedData.setLength(0);
while((amountRead = isr_reader.read(streamBuf)) > 0 || amountRead < 1){
if (amountRead > 0)
receivedData.append(streamBuf, 0, amountRead);
}
}
// Process response here
}
回答问题: 是的,我收到服务器的分块响应。 由于外部限制,我正在使用原始套接字。
为代码混乱道歉 - 我从内存中重写它,似乎引入了一些错误。
所以达成的共识是我必须做(请求,请求,读取)并让服务器在我结束时关闭流,或者,如果我做(请求,读取,请求,读取)在我点击之前停止流的结尾,以便流不关闭。
答案 0 :(得分:5)
根据您的代码,您甚至可以到达处理发送第二个请求的语句的唯一时间是服务器在收到/响应第一个请求后关闭输出流(您的输入流)。
原因是您的代码应该只读取第一个响应
while((amountRead = isr_reader.read(streamBuf)) > 0) {
receivedData.append(streamBuf, 0, amountRead);
}
将阻塞,直到服务器关闭输出流(即read
返回-1
时)或直到套接字上的读取超时为止。在读取超时的情况下,将抛出异常,您甚至不会发送第二个请求。
HTTP响应的问题在于它们不会告诉您在响应结束之前要从流中读取多少字节。这对于HTTP 1.0响应来说并不是什么大问题,因为服务器只是在响应之后关闭连接,从而使您能够通过简单地读取所有内容直到流的末尾来获取响应(状态行+标题+正文)。
使用HTTP 1.1持久连接,您不能再直接读取所有内容,直到流结束。首先需要逐行读取状态行和标题,然后根据状态代码和标题(例如Content-Length)决定读取多少字节以获取响应主体(如果它出现在所有)。如果您正确执行上述操作,您的读取操作将在连接关闭或超时发生之前完成,您将完全读取服务器发送的响应。这将使您能够发送下一个请求,然后以与第一个请求完全相同的方式读取第二个响应。
P.S。请求,请求,读取可能是“工作”,因为您的服务器支持请求流水线操作,因此,接收并处理这两个请求,因此,您将两个响应读取到一个缓冲区作为“第一”响应。
P.P.S确保您的PrintWriter
使用US-ASCII
编码。否则,根据您的系统编码,HTTP请求的请求行和标头可能格式错误(编码错误)。
答案 1 :(得分:3)
编写一个尊重RFC的简单http / 1.1客户端并不是一项艰巨的任务。 要解决在java中读取套接字时阻塞i / o访问的问题,必须使用java.nio类。 SocketChannels可以执行非阻塞的I / O访问。
这是在持久连接上发送HTTP请求所必需的。
此外,nio课程将提供更好的表现。
我的压力测试给出了以下结果:
HTTP / 1.0(java.io) - &gt; HTTP / 1.0(java.nio)= + 20%更快
HTTP / 1.0(java.io) - &gt; HTTP / 1.1(具有持久连接的java.nio)= + 110%更快
答案 2 :(得分:0)
确保您的请求中有Connection: keep-alive
。这可能是一个有争议的问题。
服务器返回什么样的响应?你在使用分块转移吗?如果服务器不知道响应正文的大小,则它不能提供Content-Length
标头,并且必须关闭响应主体末尾的连接以向客户端指示内容已结束。在这种情况下,keep-alive将不起作用。如果您使用PHP,JSP等动态生成内容,则可以启用输出缓冲,检查累积体的大小,推送Content-Length
标头并刷新输出缓冲区。
答案 3 :(得分:0)
您使用原始套接字而不是Java的URL连接或Commons HTTPClient是否有特殊原因?
HTTP很难搞定。我知道Commons HTTP Client可以像您尝试的那样重新使用连接。
如果您没有使用套接字的具体原因,我建议这样做:)
答案 4 :(得分:0)
编写自己正确的客户端HTTP / 1.1实现并不重要;历史上,我见过的大多数人都试图弄错了。它们的实现通常忽略了规范,只是看起来与一个特定的测试服务器一起工作 - 特别是,它们通常忽略了能够处理分块响应的要求。
编写自己的HTTP客户端可能是个坏主意,除非你有一些非常奇怪的要求。