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 标志(最后一部分响应体)

通过抓包工具,我们也可以证实这一过程:

HTTP2 帧

总结

总结一下 HTTP 协议中响应结束的识别机制:

  • HTTP/1.1:主要依靠 Content-LengthTransfer-Encoding: chunked 来识别
  • HTTP/2:通过流的 END_STREAM 标志来明确标识响应结束

这种设计上的差异反映了 HTTP/2 在协议层面的重大改进,使得数据传输更加可靠和高效。