HTTP 响应结束的识别机制
在一次问题排查中,我发现了一个有趣的现象:某些 HTTP 请求的响应头中既没有 Content-Length
字段,也没有 Transfer-Encoding
字段。按照我的认知,浏览器是通过这两个字段来识别响应是否结束,在缺少这两个字段的情况下,浏览器是如何确认响应结束的呢?
问题的发现
我在使用抓包工具查看 HTTP 响应时,注意到有些 HTTP 请求仅有以下响应头:
alt-svc: h3=":443"; ma=2592000
content-encoding: gzip
content-type: text/css; charset=utf-8
last-modified: Mon, 02 Jun 2025 07:31:43 GMT
vary: Accept-Encoding
x-frame-options: sameorigin
在经过一番搜索后,我才知道原来 HTTP/2 已经使用了新的方式来识别响应是否结束。
HTTP/1.1 中的响应结束识别
首先我们来回顾一下 HTTP/1.1 是如何识别响应结束的。
Content-Length
最直接的方式是通过 Content-Length
头指定响应体的确切字节数:
HTTP/1.1 200 OK
Content-Length: 123
Content-Type: text/html
<html>...</html>
浏览器读取到指定的字节数后就知道响应结束了。
Transfer-Encoding
当响应体长度在发送时无法确定时,可以使用分块传输编码:
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: plain/text
3
abc
5
abcde
0
每个块以十六进制数字开头表示块的大小,最后以大小为 0 的块表示结束。
HTTP/2 中的响应结束识别
HTTP/2 采用了完全不同的方式来处理如何标识响应结束这个问题。
HTTP/2 引入了”流”(Stream)的概念,每个请求-响应对都在一个独立的流中传输,每一条流使用 END_STREAM
标志来明确标识流的结束:
- 当服务器发送完所有响应数据后,会在最后一个 DATA 帧中设置
END_STREAM
标志 - 浏览器收到带有
END_STREAM
标志的帧后,就知道响应结束了
因此,在 HTTP/2 协议中,每次请求-响应的过程就像是这样:
HEADERS 帧(包含响应头)
DATA 帧(包含响应体的一部分)
DATA 帧(包含响应体的另一部分)
...
DATA 帧 + END_STREAM 标志(最后一部分响应体)
通过抓包工具,我们也可以证实这一过程:
总结
总结一下 HTTP 协议中响应结束的识别机制:
- HTTP/1.1:主要依靠
Content-Length
、Transfer-Encoding: chunked
来识别 - HTTP/2:通过流的
END_STREAM
标志来明确标识响应结束
这种设计上的差异反映了 HTTP/2 在协议层面的重大改进,使得数据传输更加可靠和高效。