在 HTTP 中传输数据有一个 chunked 的方式, 又称“分块传输”。在响应报文里用头字段Transfer-Encoding: chunked 来表示。意思是报文里的 body 部分不是一次性发过来的,而是分成了许多的块(chunk)逐个发送。而 HTTP2.0 协议作为 HTTP协议的升级,自然是对chunked模式做支持?不然!

HTTP2 是没有 chunked 的!

分块传输也可以用于“流式数据”,例如由数据库动态生成的表单页面,这种情况下 body 数据的长度是未知的,无法在头字段“Content-Length”里给出确切的长度,所以也只能用 chunked 方式分块发送。

chunked 的编码规则

  • 每个分块包含两个部分,长度头和数据块;
  • 长度头是以 CRLF(回车换行,即rn)结尾的一行明文,用 16 进制数字表示长度;
  • 数据块紧跟在长度头后,最后也用 CRLF 结尾,但数据不包含 CRLF;
  • 最后用一个长度为 0 的块表示结束,即“0rnrn”

HTTP2 下的分块传输

先说结论,HTTP2 是不支持 HTTP1 语义下的 chunked 模式的。因为H2的 Data 帧是纯天然的Chunked模式。

这也是最容易出bug的地方,一些实现不完全的HTTP2开源库经常在这里出问题。因为我们线上都是客户端请求基本都是 chunked 模式,升级到 HTTP2 之后,经常访问一些三方链接访问卡死,最终 debug 后的原因发现出在服务端对 HTTP2 chunked  的支持上。

最常见的反向代理实现 Nginx 就是最容易有这种 bug的,很多企业维护的Nginx经常不更新,而低版本的Nginx在HTTP2上的这个bug就被我们遇到过。

具体现象是,客户端使用chunked模式上传,但是服务端开启了 HTTP2,自然客户端也就升级到 HTTP2 。但是请求总是卡主,服务端无响应。最终定位到如果 DATA帧不携带内容,只携带一个 End Stream 标志,服务端无法识别流式传输结束。我拿 www.bing.com 做了对比,其他网站都是正常的。

其实 RFC 7540 早有规定,HTTP2 的传输是不支持 Transfer-Encoding: chunked 的。

一般的网络库(如 Cronet、OKHTTP )底层都给我们做了兼容,如果上层使用 chunked 模式传输,而实际使用的是 HTTP2 ,网络库会帮我们自动隐藏掉 Transfer-Encoding: chunked 这个 Header 。

使用 HTTP2 发送 chunked

这里我们以 Golang 的 HTTP2 官方的客户端库做一个测试.

通过导入 SSLKEYLOG 从 Wireshark 抓包可以看到,每个 Data Frame 大小为 8000 。

HTTP2 下的 Transfer-Encoding: chunked
标签: