当java向服务器post请求时,首部中有一个Content-length字段,即请求主体中的字节数。首部在主体的前面。不过,要写入首部,需要知道主体的长度,而在写首部的时候可能还不知道主体的长度。正常情况下,对于这个两难的问题。
如图所示
Java的解决办法是:对于从HttpURLConnection获取的OutputStream ,将写入此OutputStream的所有内容缓存,直到流关闭。此时它就会知道主体中有多少字节,所以有足够的信息来写入Content-length首部。
这种模式对于响应典型Web表单的短请求很合适。不过,对于非常长的表单或一些SOAP消息,响应时负担会很大。用HTTP PUT发送中等到大型文挡时会很浪费,也很慢。如果Java 通过网络发送第一字节之前,不需要等待写入最后一字节,将会高效得多。
Java 为这个问题提供了两种解决方案。
解决方案1:如果你知道数据的大小,例如使用HTTP PUT上传一个已知大小的文件,可以将数据的大小告诉HttpURLConnection对象。
解决方案2:如果预先不知道数据大小,可以使用分块传输编码方式。在分块传输编码方式中,请求主体以多个部分发送,每个部分都有自己单独的内容长度。要启用分块传输编码方式,只要在连接URL之前将分块大小传入setChunkedStreamingMode()方法(需要服务器支持分块传输编码方式,所以,,除非确实需要,否则不要使用分块传输编码方式)。
如果恰好预先知道请求数据的大小,可以将这个信息提供给HttpURLConnection对象,从而优化连接。如果这样做,Java会立即通过网络将数据以流方式发送。否则,它必须缓存你写入的所有数据来确定内容长度,而且只有在你关闭流之后才能通过网络发送数据。如果知道数据的具体大小,可以将这个数传递给setFixedlengthStreamingMode()方法。由于这个数可能实际大于int所能存储的最大整数(大约21亿个字节,大约200M),所以在Java 7及以后版本中可以使用一个long。
注意两个问题:
- Java会在HTTP首部的Content-length字段中使用这个数。不过,如果接下来试图写入的数据多于或少于给出的这个字节数, Java会抛出一个IOException异常。
- 流模式确实会妨碍身份认证和重定向。如果给定的URL要求认证或重定向,就会抛出一个HttpRetryException异常