最近给一个tomcat服务加上nginx代理,陆续遇到一些问题(坑)。
第一个坑,一个接口直接访问正常,经nginx代理后报104错误(104: Connection reset by peer)。奇葩之处在于,只有特定的接口出现这种错。
先说解决方案:配置反向代理长链接
很容易搜到104错误的解决方案.在nginx配置中,加上下面两句:
proxy_http_version 1.1;
proxy_set_header Connection "";
加上之后,执行命令 nginx -s reload 生效。
翻看nginx官方文档,这么说:
For HTTP, the proxy_http_version directive should be set to “1.1” and the “Connection” header field should be cleared:
upstream http_backend {
server 127.0.0.1:8080;
keepalive 16;
}
server {
...
location /http/ {
proxy_pass http://http_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
...
}
}
到这里,问题已经解决。我们再进一步,看看配置前后的区别。
tcpdump 抓包
在修改前后,各自抓包。
tcpdump -iensxxx -vvvs0 -l -A ‘tcp port 80 or tcp port 8082’ -w tcpdump.cap
wireshark分析
将*.cap文件用wireshark 打开.
- 在filter里输入 http.request && http contains “HTTP/1.0” ,过滤出nginx到上游服务的请求.
- 随意选中一条记录。右键,”追踪流…” -> “TCP流”。这样,筛选出了TCP会话的所有包记录。
- 右键, “复制” -> “摘要为文本”,可以将报文导出为文本。
下面的记录,第一条是异常响应报文,第二条是正常响应报文。
--- not ok tcp.stream eq 75
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/json;charset=UTF-8
Date: Mon, 23 Aug 2021 09:41:57 GMT
Connection: close
[{"xxxxxx":"xxxxxxxxxxxxxxx","xxxx":"xxx","xxxxxx":"xxxxxxxxxxxxx","xxx":xx}]
--- ok tcp.stream eq 104
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Mon, 23 Aug 2021 09:45:33 GMT
4d
[{"xxxxxx":"xxxxxxxxxxxxxxx","xxxx":"xxx","xxxxxx":"xxxxxxxxxxxxx","xxx":xx}]
可以观察到:
- 头部不一致。
- 异常报文多了 “Connection: close” ,含义是数据返回后将关闭连接。
- 正常报文多了 “Transfer-Encoding: chunked”,含义是返回的数据采取“分块编码”, 也就是分多块返回。
- body不一致。
- 正常报文在body之前,声明了数据块的大小。
- 我们使用HTTP1.0发起请求,但返回报文头部居然写着 HTTP/1.1。这是怎么回事?? 参考 https://stackoverflow.com/questions/19461312/why-tomcat-reply-http-1-1-respose-with-an-http-1-0-request, 返回头的HTTP版本号只是一个声明,告知调用方服务端所支持的最高HTTP版本。
This Connector supports all of the required features of the HTTP/1.1 protocol, as described in RFC 2616, including persistent connections, pipelining, expectations and chunked encoding. If the client (typically a browser) supports only HTTP/1.0, the Connector will gracefully fall back to supporting this protocol as well. No special configuration is required to enable this support. The Connector also supports HTTP/1.0 keep-alive.
RFC 2616 requires that HTTP servers always begin their responses with the highest HTTP version that they claim to support. Therefore, this Connector will always return HTTP/1.1 at the beginning of its responses.
为什么有的接口不报错?
在此之前,先思考一个问题:http协议是基于tcp的,tcp本身不处理数据包的边界,那客户端怎么做到恰到好处地读取数据?
无外乎两种方案:1. 在报文中,先声明数据大小,客户端读满为止;2.约定一个边界,客户端看到这个记号再停。
HTTP/1.0中,采用第一种方案,在HTTP头部利用 “Content-Length”声明http body的长度;
HTTP/1.1协议引入 “Transfer-Encoding: chunked”头。浏览器不需要等到内容字节全部下载完成,只要接收到一个chunked块就可解析页面。这种情况下,每个chunk块之前会声明chunk大小,之后有分隔符。
有了这些思考,再用wireshark来分析HTTP/1.0请求的返回报文。正常接口返回的TCP报文,虽然HTTP头部没有声明Content-Length,但因为数据量大,被拆分为若干个Frame。报错接口返回的报文,数据量很小。
我的推测是:虽然它们都没有显式声明大小或者边界,但是当数据量大到被拆分为多个Frame时,客户端能基于Frame解析出来。