HttpURLConnection:为什么在指定 Expect:100-Continue Header 时每个块有 5 秒的延迟?

HttpURLConnection : Why is there 5 seconds delay with every chunk when Expect:100-Continue Header is specified?

我正在试验 RFC Expect Header in java HttpURLConnection 除了一个细节外,它工作得很好。

发送 Fixed Length Mode or between each chunk in Chunk Streaming Mode

的 body 之间有 5 秒的等待时间

这里是客户端class

public static void main(String[] args)throws Exception
 {
   HttpURLConnection con=(HttpURLConnection)new URL("http://192.168.1.2:2000/ActionG").openConnection();
   
   //for 100-Continue logic
   con.setRequestMethod("POST");
   con.setRequestProperty("Expect", "100-Continue");
  
   //responds to 100-continue logic
   con.setDoOutput(true);
   con.setChunkedStreamingMode(5);
   con.getOutputStream().write("Hello".getBytes());
   con.getOutputStream().flush();
   con.getOutputStream().write("World".getBytes());
   con.getOutputStream().flush();
   con.getOutputStream().write("123".getBytes());
   con.getOutputStream().flush();

   //decode response and response body/error if any
   System.out.println(con.getResponseCode()+"/"+con.getResponseMessage());      
   con.getHeaderFields().forEach((key,values)->
   {
    System.out.println(key+"="+values);
    System.out.println("====================");
   });      
   try(InputStream is=con.getInputStream()){System.out.println(new String(is.readAllBytes()));}
   catch(Exception ex)
   {
    ex.printStackTrace(System.err);
   
    InputStream err=con.getErrorStream();
    if(err!=null)
    {
     try(err){System.err.println(new String(is.readAllBytes()));}
     catch(Exception ex2){throw ex2;}
    }  
   } 
   con.disconnect();
  }

我正在上传 3 个块。服务器端收到5包数据

  1. 全部headers。回复 100 继续

  2. 3 个块。对于每个块响应 100 Continue

  3. 最后一个区块[长度 0]。回复 200 OK

这是测试服务器

final class TestServer
{
 public static void main(String[] args)throws Exception
 {
  try(ServerSocket socket=new ServerSocket(2000,0,InetAddress.getLocalHost()))
  {
   int count=0;
   try(Socket client=socket.accept())
   {
    int length;
    byte[] buffer=new byte[5000];
    InputStream is=client.getInputStream();
    
    OutputStream os=client.getOutputStream();
    while((length=is.read(buffer))!=-1)
    {
     System.out.println(++count);
     System.out.println(new String(buffer,0,length));
     System.out.println("==========");
  
     if(count<5)
     {
      os.write("HTTP/1.1 100 Continue\r\n\r\n".getBytes());
      os.flush();
     }
     else
     {
      os.write("HTTP/1.1 200 Done\r\nContent-Length:0\r\n\r\n".getBytes());
      os.flush();
      break;
     }   
    }
   }
  }
 }
}

输出:

1
POST /ActionG HTTP/1.1
Expect: 100-Continue
User-Agent: Java/17.0.2
Host: 192.168.1.2:2000
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: keep-alive
Content-type: application/x-www-form-urlencoded
Transfer-Encoding: chunked


==========   //5 seconds later
2
5
Hello

==========   //5 seconds later
3
5
World

==========  //5 seconds later
//this and the last chunk come seperatly but with no delay
4
3
123

==========
5
0


==========

我已经检查了连接中的每个超时方法Object

System.out.println(con.getConnectTimeout());
System.out.println(con.getReadTimeout());

两个return 0

那么这 5 秒的延迟是从哪里来的呢?

我正在使用 jdk 17.0.2 和 windows 10

3 Chunks. For each chunk respond with 100 Continue

那不是 Expect: 100-Continue 的工作方式。您的服务器代码对于您尝试执行的操作是完全错误的。实际上,对于一般的 HTTP 服务器,您的服务器代码是完全错误的。它甚至根本不尝试解析 HTTP 协议。不是 HTTP headers,不是 HTTP 块,什么都不是。您是否没有使用实际的 HTTP 服务器实现,例如 Java 自己的 HttpServer?

当使用 Expect: 100-Continue 时,客户端只需要发送请求 headers,然后停止并等待几秒钟以查看服务器是否发送 100 响应或不是:

  • 如果服务器响应100,客户端可以通过发送请求body完成请求,然后接收最终响应。

  • 如果服务器响应 100 以外的任何内容,客户端可能会立即失败而不发送请求 body。

  • 如果没有收到响应,客户端可以通过发送请求body完成请求并接收最终响应。

Expect: 100-Continue 的全部要点是客户端在发送大请求之前请求许可 body。如果服务器不想要 body(即 headers 描述不满意的条件等),客户端不必浪费精力和带宽来发送请求 body只会被拒绝。

HttpURLConnection built-in 支持处理 100 响应,但请参阅 How to wait for Expect 100-continue response in Java using HttpURLConnection for caveats. Also see JDK-8012625: Incorrect handling of HTTP/1.1 " Expect: 100-continue " in HttpURLConnection

但是,如图所示的服务器代码需要进行重大重写才能正确处理 HTTP,更不用说处理分块请求正确

非常感谢@Remy Lebeau 就如何正确解析此特殊内容提供的见解 header。我注意到在创建一个基本解析器并正确响应分块 [Transfer-Encoding:chunked header] 和固定长度流 [Content-Length header] header 之后有时我的客户仍然会卡住并偶尔等待 5 秒,有时会正常工作。

经过数小时的 com.sun.www.http.HttpURLConnection class 调试后,我意识到另一个缺陷不再出现在服务器端,而是实际上出现在代码的客户端。尤其是这一点

con.getOutputStream().write("Hello".getBytes());
con.getOutputStream().flush();
con.getOutputStream().write("World".getBytes());
con.getOutputStream().flush();
con.getOutputStream().write("123".getBytes());
con.getOutputStream().flush();

我错误地认为 class 中的 getOutputStream() 会缓存返回的输出流,但实际上它 returns 每次都是一个新的输出流。

所以我不得不将代码更改为这个

OutputStream os=client.getOutputStream();
os.write("Hello".getBytes());
os.flush();
os.write("World".getBytes());
os.flush();
os.write("123".getBytes());
os.flush();

这终于解决了我所有的问题。适用于分块和固定长度流