http-transfer-encoding探秘
缘起
公司有一些后台系统支持导出Excel,导出量大的时候机器直接oom了。调用流程大概如下:
db ——> tomcat ——> http协议 ——> client
最初的开发人员,似乎并没有考虑这个问题,从db到tomcat都是一个sql直接select的。
有同事改进了一版,采用分页查询,然后用支持流式写入的excel工具。但是这样真的就不会oom了吗?
其实得整条链路上都是流式的才可以,读到一部分数据,立马发出去,这部分内存释放了,也就不会oom了。
所以还差tomcat到client的流式传输,那就不得不说到Content-Lenght
和Transfer-Encoding
这俩参数了。
小实验
写一个普通的servlet,代码如下:
1 | /** |
然后curl一下,看下结果:
post接口
1 | ➜ qsli.github.com curl -XPOST "http://localhost:8080/servlet/foo" -v |
这个post接口,返回的header中直接告诉了我们Content-Length
是11,抓包看下传输过程是否分块:
response在frame-7中:
一个tcp就把结果返回了。
get接口
1 | ➜ qsli.github.com curl -XGET "http://localhost:8080/servlet/foo" -v |
这次并没有Content-Length
,但是多了个Transfer-Encoding
,抓包结果如下:
结果在frame-19
可以看到有三个Data chunk
,每个chunk
的组成是三部分:
- Chunk size
- Data
- Chunk boundary
wireshark自动帮我们聚合展示了,看具体的tcp包:
分别在Frame-9和Frame-19中,Frame-9传了header信息和hello,Frame-19的包传了world和结束信息。
why?
先看下两个的定义:
Content-Lenght
The
Content-Length
entity header indicates the size of the entity-body, in bytes, sent to the recipient.Content-Length:
The length in decimal number of octets.
Transfer-Encoding
The
Transfer-Encoding
header specifies the form of encoding used to safely transfer the payload body to the user.HTTP/2 doesn’t support HTTP 1.1’s chunked transfer encoding mechanism, as it provides its own, more efficient, mechanisms for data streaming.
Chunked encoding is useful when larger amounts of data are sent to the client and the total size of the response may not be known until the request has been fully processed. For example, when generating a large HTML table resulting from a database query or when transmitting large images.
Tranfer-encoding需要配合长连接来使用:
暂时把 Transfer-Encoding 放一边,我们来看 HTTP 协议中另外一个重要概念:Persistent Connection(持久连接,通俗说法长连接)。我们知道 HTTP 运行在 TCP 连接之上,自然也有着跟 TCP 一样的三次握手、慢启动等特性,为了尽可能的提高 HTTP 性能,使用持久连接就显得尤为重要了。为此,HTTP 协议引入了相应的机制。
HTTP/1.0 的持久连接机制是后来才引入的,通过
Connection: keep-alive
这个头部来实现,服务端和客户端都可以使用它告诉对方在发送完数据之后不需要断开 TCP 连接,以备后用。HTTP/1.1 则规定所有连接都必须是持久的,除非显式地在头部加上Connection: close
。所以实际上,HTTP/1.1 中 Connection 这个头部字段已经没有 keep-alive 这个取值了,但由于历史原因,很多 Web Server 和浏览器,还是保留着给 HTTP/1.1 长连接发送Connection: keep-alive
的习惯。
Tomcat中的实现
为什么flush之后就变成了chunked传输?这里tomcat的源码版本是7.0.47
,对应的Response是
1 | // org.apache.catalina.connector.OutputBuffer#doFlush |
最后直接调用socket的flush:
之后就是系统的TCP/IP协议栈处理了。