侧边栏壁纸
博主头像
一笑痕

人生若只如初见,
是可喜亦或者是可悲?

  • 累计撰写 184 篇文章
  • 累计收到 7 条评论

180 字节的响应体,头要付 2086 字节

2026-10-13 / 0 评论 / 6 阅读

180 字节的响应体,头要付 2086 字节

同一个 180 字节的响应体,不带 Cookie 时一个来回 456 字节,其中请求头 148、响应头 128,体占 39.5%。把 Cookie 加到 1800 字节,同一个服务端、同一个体,一个来回变成 2266 字节,体只占 7.9%,剩下 2086 字节全是头。

账是在本机两台服务端上数的:8081 明文吐体,8082 在吐之前先过一遍 gzip。字节数都在 socket 层记,请求头数到结束的那个空行为止,响应头数自己拼出来的状态行加响应头。客户端用 node 的 http.request 真发请求,两端各数一遍。

~/.hermes/node/bin/node tools/verify-header-bytes.mjs > tools/verify-header-bytes.log

不带 Cookie,头也已经比体大

先看最干净的一组。响应体是 180 字节的 JSON,登录之后返回的那种,token 用固定随机串填满,免得重复内容在压缩里占便宜。请求走的 GET,没有请求体。

== 一次 HTTP 往返在网线上的字节账 ==
node v26.8.1,服务端 127.0.0.1:8081 明文 / 127.0.0.1:8082 gzip
请求头 = 请求行与全部请求头,到结束空行的原始字节
响应头 = 状态行与全部响应头,到结束空行的原始字节
两边都只算 HTTP 报文本身,不含 TCP/IP 包头;请求都是 GET,没有请求体

== A 响应体固定 180 字节,不带 Cookie ==
  A1 明文 8081  请求头 148 字节  响应头 128 字节  体 180 字节  一个来回 456 字节  体占 39.5%
  A2 gzip 8082  请求头 148 字节  响应头 175 字节  体 186 字节  一个来回 509 字节  体占 36.5%
  A2 的体原始 180 字节,gzip 后 186 字节,+3.3%,gzip 多出的 6 字节是压缩头本身
  A1 去掉体只剩 276 字节的头,占整个来回的 60.5%

明文那组,请求头 148 字节,响应头 128 字节,体 180 字节,合计 456。头 276 字节占 60.5%,比体多了将近一百字节。请求头比响应头还大一点,多出来的是 User-Agent、Accept 这些每次都要挂上的字段。

gzip 那组,请求头一模一样 148 字节,体的原始大小也一样,压在线上却是 186 字节,涨了 6 字节。

1800 字节的 Cookie 是怎么算进账的

Cookie 躺在请求头里,它涨多少,请求头就涨多少,绕不开。三档 Cookie 一起跑,体一个字节没变。

== B Cookie 从 300 涨到 1800 字节,体还是 180 字节(明文 8081) ==
  B Cookie 300 字节  请求头 458 字节(其中 Cookie 300)  响应头 128 字节  体 180 字节  一个来回 766 字节  体占 23.5%
  B Cookie 900 字节  请求头 1058 字节(其中 Cookie 900)  响应头 128 字节  体 180 字节  一个来回 1366 字节  体占 13.2%
  B Cookie 1800 字节  请求头 1958 字节(其中 Cookie 1800)  响应头 128 字节  体 180 字节  一个来回 2266 字节  体占 7.9%
  Cookie 从 300 到 900 字节,请求头涨 600 字节,一个来回涨 600 字节
  Cookie 从 900 到 1800 字节,请求头涨 900 字节,一个来回涨 900 字节
  Cookie 1800 时请求头 1958 字节,是响应体 180 字节的 10.9 倍
  同样 180 字节的响应,从无 Cookie 到 Cookie 1800,一个来回从 456 涨到 2266 字节,+396.9%

三条线是直的。Cookie 从 300 涨到 900,请求头从 458 涨到 1058,差 600;再从 900 涨到 1800,请求头从 1058 涨到 1958,差 900。塞进去多少字节,账上就是多少字节。

体在这一组里没变,还是 180 字节,但份额从 23.5% 掉到 13.2%,再掉到 7.9%。Cookie 1800 那次的请求头是响应体的 10.9 倍。同一个接口、同一份数据,只是请求多带了一张通行证,网线上的字节就翻了好几倍。

Cookie 是按请求带的,一个域名下二十个静态资源,每个请求都会把通行证再抄一遍。

gzip 压不动的那一半

gzip 只作用在响应体上,请求头不归它管。把体撑到 16384 字节,它的用武之地和边界就一起露出来了。

== C 体换成 16384 字节,gzip 只动体 ==
  C1 明文 8081  请求头 146 字节  响应头 130 字节  体 16384 字节  一个来回 16660 字节
  C2 gzip 8082  请求头 146 字节  响应头 175 字节  体 168 字节  一个来回 489 字节
  C2 的体原始 16384 字节,gzip 后 168 字节,1.0%,省下 16216 字节
  两个端口的请求头都是 146 字节,一模一样,gzip 没有碰请求头
  同样的体,gzip 端口响应头 175 字节,明文端口 130 字节,多出的 45 字节是 Content-Encoding 与 Vary 两行

明文那组一个来回 16660 字节,其中 16384 是体。gzip 那组,体从 16384 压到 168,只剩 1.0%,一个来回掉到 489 字节。

请求头那行没变,两个端口都是 146 字节。gzip 对 Cookie、User-Agent、Authorization 一个字节都压不掉,请求头压根不在压缩范围里。响应头反而多出 45 字节,是加上的 Content-Encoding 和 Vary 两行。

180 字节那一组为什么 gzip 帮倒忙,答案也在这里:压缩的收益跟着体的长度走,体小到一定程度,压缩头本身就比省下来的多。服务端一般会对太小的响应跳过压缩。

404 和 304 没有体,头照付

还有一类请求,响应里一个字节的体都没有,头却一分不少。

== D 无体响应:404 与 304 依然要付头的钱 ==
  D1 404 无 Cookie  状态 404  请求头 150 字节  响应头 101 字节  体 0 字节  一个来回 251 字节
  D2 404 Cookie 1800  状态 404  请求头 1960 字节  响应头 101 字节  体 0 字节  一个来回 2061 字节
  D3 200 Cookie 1800  状态 200  请求头 1957 字节  响应头 140 字节  体 180 字节  一个来回 2277 字节
  D4 304 Cookie 1800  状态 304  请求头 1978 字节  响应头 97 字节  体 0 字节  一个来回 2075 字节
  D1 的体是 0 字节,一个来回全是头:251 字节
  D2 带上 Cookie 1800 后,一个来回 2061 字节,是 D1 的 8.2 倍,两边的响应都只有一行状态
  D3 与 D4 走同一个地址,D4 多了 If-None-Match 一行共 21 字节,换掉 180 字节的体
  304 相对 200 净省 202 字节:去掉 180 字节的体,加回 21 字节的 If-None-Match,再省掉 Content-Type 与 Content-Length 两行
  Cookie 1800 的 304 一个来回 2075 字节,还比不带 Cookie 的 200 带体请求(456 字节)多 1619 字节

不带 Cookie 的 404,一个来回 251 字节,全是头。把 Cookie 加回 1800 字节,还是那个 404,一个来回 2061 字节,涨到 8.2 倍,两边返回的东西却完全一样。

304 是同一本账。D3 返回 200 带 180 字节的体,D4 挂上 If-None-Match 换回 304,走的同一个地址。304 去掉体、再省掉 Content-Type 和 Content-Length 两行,净省 202 字节,可来回仍然是 2075 字节,比不带 Cookie 的 456 字节多出 1619。缓存命中省下的是体,头按原价收。

心跳和版本检查这类接口会把这个账放大,它们的响应常常只有几十字节,带上完整 Cookie,成本几乎清一色是头。

两端的读数对得上

每个请求都在两端各记一遍账:服务端按收到的原始报文数,客户端按 socket 上的读写字节数。

== E 交叉核对:服务端按原始报文数的字节,对客户端 socket 的读写字节 ==
  A1 明文 8081  请求头 148/148  响应头 128/128  体 180/180  相等
  A2 gzip 8082  请求头 148/148  响应头 175/175  体 186/186  相等
  B Cookie 300 字节  请求头 458/458  响应头 128/128  体 180/180  相等
  B Cookie 900 字节  请求头 1058/1058  响应头 128/128  体 180/180  相等
  B Cookie 1800 字节  请求头 1958/1958  响应头 128/128  体 180/180  相等
  C1 明文 8081  请求头 146/146  响应头 130/130  体 16384/16384  相等
  C2 gzip 8082  请求头 146/146  响应头 175/175  体 168/168  相等
  D1 404 无 Cookie  请求头 150/150  响应头 101/101  体 0/0  相等
  D2 404 Cookie 1800  请求头 1960/1960  响应头 101/101  体 0/0  相等
  D3 200 Cookie 1800  请求头 1957/1957  响应头 140/140  体 180/180  相等
  D4 304 Cookie 1800  请求头 1978/1978  响应头 97/97  体 0/0  相等
  两端读数逐项相等,11 组全部对得上
  服务端共记录 11 个请求(明文 9 / gzip 2),没有漏记
== 结束 ==

十一组数字逐项相等,请求头那一栏、响应头那一栏两端都对得上。

有一处我没往下走。Cookie 只记了总字节数,没拆开看它是几个 Cookie 拼出来的,也没测跨域场景下同一个域会不会被重复携带。单条连接上的一个来回已经足够说明问题:Cookie 撑到 1800 字节,头在整趟传输里的开销是体的十倍以上。

真要动这块,无非几件事:给 Cookie 定一个字节上限,把静态资源挪到不挂 Cookie 的域名下,无体响应别在响应头里塞多余的东西。代价都不在字节上,在改造量上。

    🤞 分享