Java ScoketChannel忽略内容长度并使用传输编码:基于用户代理分块

Java ScoketChannel忽略内容长度并使用传输编码:基于用户代理分块,java,tomcat,content-length,chunked,Java,Tomcat,Content Length,Chunked,我想压缩javax.servlet.Filter中的响应体。这是我的密码 byte[] bytes = // compressing response body response.addHeader("Content-Encoding", "gzip"); response.addHeader("Content-Length", String.valueOf(bytes.length)); response.setContentLength(bytes.length); response.se

我想压缩javax.servlet.Filter中的响应体。这是我的密码

byte[] bytes =  // compressing response body
response.addHeader("Content-Encoding", "gzip");
response.addHeader("Content-Length", String.valueOf(bytes.length));
response.setContentLength(bytes.length);
response.setBufferSize(bytes.length * 2);
ServletOutputStream output = response.getOutputStream();
output.write(bytes);
output.flush();
output.close();
但我在Chrome开发工具中看到的实际反应是

Accept-Ranges: bytes
Cache-Control: max-age=2592000
Content-Type: application/javascript;charset=UTF-8
Date: Fri, 14 Dec 2018 15:34:25 GMT
Last-Modified: Tue, 09 Oct 2018 13:42:54 GMT
Server: Apache-Coyote/1.1
Transfer-Encoding: chunked
我不期望传输编码:分块,因为我声明“内容长度”。我在java上编写了一个简单的测试

URLConnection connection = new URL("http://127.0.0.1:8081/js/ads.js").openConnection();
connection.addRequestProperty("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8");
connection.addRequestProperty("Accept-Encoding", "gzip, deflate");
connection.addRequestProperty("Accept-Language", "ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7");
connection.addRequestProperty("Cache-Control", "no-cache");
connection.addRequestProperty("Connection", "keep-alive");
connection.addRequestProperty("Host", "127.0.0.1:8081");
connection.addRequestProperty("Pragma", "no-cache");
connection.addRequestProperty("Upgrade-Insecure-Requests", "1");
connection.addRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36"); 
connection.connect();
connection.getHeaderFields().forEach((s, strings) ->
        System.out.println(s + ":" + String.join(",", strings)));
以下是我的发现:

  • 如果我对设置“用户代理”标题进行注释或将“用户代理”更改为任何其他值,那么我将得到带有“内容长度”的响应
  • 如果“用户代理”指向Chrome,那么我会得到传输编码:chunked
我调试了sun.nio.ch.SocketChannel#write方法,它使用Content-Length头值获得正确的ByteBuffers

我无法理解这种神奇的转换在哪里发生

更新

奇怪的是,我将gziped字节写入套接字(我确信在SocketChannel实现中调试了本机方法write的调用)。但是URLConnection返回带有Chrome用户代理的解压缩的字节数组,如果我没有指定用户代理头或放置一些随机字符串,则返回正确的gzip字节数组。
因此,在Windows套接字实现中似乎发生了神奇的事情。

显示了代码

我会假设您显示的代码是有效的,问题出在其他地方

设置

  • 视窗10
  • Tomcat 7.0.92
  • 铬71.0.3578.98
测试用例

我试图创建一个小的过滤器示例,以便能够尝试您的测试代码

顺便说一句,更适合生产使用的压缩过滤器可以在 Tomcat附带的示例(webapps\examples\WEB-INF\classes\compressionFilters)

结果

Chrome的开发者工具中显示了三个静态html、JSP生成的页面和Servlet生成的页面以及一些虚拟内容的测试用例:

a) 使用静态html

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Accept-Ranges: bytes
ETag: W/"108-1545775482914"
Last-Modified: Tue, 25 Dec 2018 22:04:42 GMT
Content-Encoding: gzip
Content-Type: text/html
Content-Length: 97
Date: Tue, 25 Dec 2018 22:34:41 GMT
b) JSP生成

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Encoding: gzip
Content-Type: text/html
Content-Length: 38
Date: Tue, 25 Dec 2018 22:49:17 GMT
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Encoding: gzip
Content-Type: text/html
Content-Length: 65
Date: Tue, 25 Dec 2018 22:49:43 GMT
c) Servlet生成

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Encoding: gzip
Content-Type: text/html
Content-Length: 38
Date: Tue, 25 Dec 2018 22:49:17 GMT
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Encoding: gzip
Content-Type: text/html
Content-Length: 65
Date: Tue, 25 Dec 2018 22:49:43 GMT

使用此设置时,没有传输编码:分块。因此,可能可以在其他地方找到此分块头的原因?

为什么要将缓冲区大小设置为文件大小的两倍?另外,尝试删除输出流上对
flush
的显式调用。我不知道缓冲区是仅用于响应体还是也包含头。所以我设置的缓冲区大小足以满足这两种情况。删除刷新没有帮助。我的调试显示,write方法调用对SocketChannel.Hmm的写入。通常,分块编码仅在您尝试执行“额外”操作时发生,例如在
输出流上调用
flush()
close()
。如果您设置
内容长度
,然后自己将文件转储到输出流,Tomcat应该尊重您的代码。Tomcat版本?客户端和Tomcat之间是否存在反向代理?缓冲区大小通常用于响应。通常没有理由设置响应缓冲区大小,除非您遇到一些奇怪的性能问题。更改响应缓冲区大小最终会永久性地更改该(重复使用的)响应对象的响应缓冲区,因此,如果返回了几个大文件,则可能会浪费大量的堆和保持较大的缓冲区。