Tomcat 偶尔 returns 没有 HTTP 的响应 headers

Tomcat occasionally returns a response without HTTP headers

我正在调查 Tomcat (7.0.90 7.0.92) return 没有 HTTP header 的响应的问题非常偶尔。

根据 Wireshark 捕获的数据包,在 Tomcat 收到请求后它只是 return 仅响应 body。它 return 既不是状态行也不是 HTTP 响应 header。

它使下游 Nginx 实例产生错误“upstream sent no valid HTTP/1.0 header while reading response header from upstream”, return 502 错误到客户端,关闭Nginx与Tomcat.

对应的http连接

导致此行为的原因是什么?是否有可能使 Tomcat 以这种方式运行?或者在某些情况下可以剥离 HTTP headers?或者 Wireshark 未能捕获包含 HTTP header 的帧?也非常感谢任何缩小问题范围的建议。

这是 Wireshark "Follow HTTP Stream" 的屏幕截图,其中显示了有问题的响应:

编辑:

这是"TCP Stream"相关部分的截图(仅回复)。似乎最后一个响应中的第二个响应中的块看起来不错:

EDIT2:

我将这个问题转发给了 Tomcat 用户邮件列表,并从开发人员那里得到了进一步调查的一些建议:

http://tomcat.10.x6.nabble.com/Tomcat-occasionally-returns-a-response-without-HTTP-headers-td5080623.html

但是我还没有找到合适的解决办法。我仍在寻找解决这个问题的见解..

我们看到您正在重新使用已建立的连接来发送 POST 请求,并且正如您所说,响应没有 status-lineheaders.

after Tomcat receives a request it just returns only a response body.

不完全是。它以 5d 开头,可能是 chunk-size,这意味着最新的 "full" 响应(status-lineheaders) 从这个连接中得到包含一个“Transfer-Encoding: chunked" header。出于任何原因,您的服务器在开始向您的上一个请求发送新响应时仍然认为之前的响应尚未完成。

由于屏幕截图未显示结束前一个请求的 last-chunk(值 = 0),因此似乎已确认缺少分块。请注意,最后一个响应以 last-chunk 结尾(显示的最后一个字节为 0)。

这是什么原因造成的?从技术上讲,先前的回复未被视为已完全回答。它可能是 Tomcat、您的网络服务库、您自己的代码中的错误。甚至,在上一个请求得到完全答复之前,您发送请求的时间过早。

如果比较 chunk-size 与实际发送给客户端的内容相比,是否缺少一些字节?是否刷新了所有缓冲区?也要注意行结尾(仅限 CRLF 与 LF)。

我正在考虑的最后一个原因是,如果您的响应包含从请求中获取的某种用户输入,您可能会面临 HTTP Splitting

可能的解决方案。

值得尝试在您的库级别禁用分块编码,例如使用 Axis2 检查 HTTP Transport

重用连接时,请检查您的客户端代码以确保您在阅读之前的所有响应之前没有发送请求(以避免重叠)。

进一步阅读

RFC 2616 3.6.1 分块传输编码

您遇到的问题源于通过与上游的单个连接对多个请求进行流水线处理,正如 Eugène Adell.

昨天在这里的回答所解释的那样

这是否是 nginx、tomcat、您的应用程序或上述任何组合的交互中的错误,可能会在另一个论坛上进行讨论,但现在,让我们考虑一下会是什么最佳解决方案:

Can you post your nginx configuration? Specifically, if you're using keepalive and a non-default value of proxy_http_version within nginx? – cnst 1 hour ago

@cnst 我正在使用 proxy_http_version 1.1keepalive 100 – Kohei Nozaki 1 小时前

根据 earlier answer to an unrelated question here on SO,但如上共享配置参数,您可能需要重新考虑在前端负载平衡器(例如 nginx)和后端应用程序服务器(例如,tomcat)。

根据 keepalive explanation on ServerFault in the context of nginx,nginx 的 upstream 上下文中的保活功能直到最近在 nginx 开发年代才得到支持。为什么?这是因为当建立新连接基本上比等待现有连接可用时使用 keepalive 的有效场景很少:

  • 当客户端和服务器之间的延迟大约为 50ms+ 时,keepalive 可以重用 TCP 和 SSL 凭据,从而实现非常显着的加速,因为没有额外的往返需要连接准备好为 HTTP 请求提供服务。

    这就是为什么你永远不应该禁用客户端和 nginx 之间的保持连接(通过 httpserverlocation 上下文中的 http://nginx.org/r/keepalive_timeout 控制)。

  • 但是当前端代理服务器和后端应用服务器之间的延迟在1ms(0.001s)数量级时,使用keepalive是一个无需追逐Heisenbugs的秘诀获得任何好处,因为建立连接的额外 1ms 延迟可能还少于等待现有连接可用的 100ms 延迟。 (这是对连接处理的过度简化,但它只是向您展示了前端负载均衡器和应用程序服务器之间 keepalive 的任何可能好处是多么微不足道,前提是它们都位于同一地区。)

    这就是为什么在 upstream 上下文中使用 http://nginx.org/r/keepalive 很少是一个好主意,除非你确实需要它,并且已经特别验证它会产生你想要的结果,给定分数如上

    (并且,为了清楚起见,这些要点与您使用的实际软件无关,因此,即使您没有遇到 nginx 和 [=72= 的组合所遇到的问题], 我仍然建议你不要在负载平衡器和应用程序服务器之间使用 keepalive,即使你决定从 nginx 和 tomcat 中的一个或两个切换。)


我的建议?

  • 默认值 http://nginx.org/r/proxy_http_versionhttp://nginx.org/r/keepalive 无法重现该问题。

  • 如果你的后端在前端的 5 毫秒以内,你肯定不会从一开始就从修改这些指令中获得任何好处,所以,除非追逐 Heisenbugs 是你的道路,否则你不妨将这些特定设置保留为最合理的默认值。

事实证明,JAX-WS RI v2.1.3 使用的 "sjsxp" 库使 Tomcat 以这种方式运行。我尝试了一个不再使用 "sjsxp" 库的不同版本的 JAX-WS RI (v2.1.7),它解决了这个问题。

Metro 邮件列表上发布了一个非常相似的问题:http://metro.1045641.n5.nabble.com/JAX-WS-RI-2-1-5-returning-malformed-response-tp1063518.html