📘 第六章 HTTP首部
📄 一句话概括

Accept-Encoding 告诉服务端「我支持哪些压缩方式」。 它也带 q 值,也遵守一条铁律:客户端不提,服务端不许自己压。

📝 本节和前面几节的关系

上一节那个字段死了,这一个活得很好——它是每个真实响应里几乎都会出现的一对字段之一。

一、几个常见取值

值 说明 现状
gzip 事实上的标准压缩 几乎都支持
deflate zlib 格式,历史上实现混乱 支持但少用
br Brotli,比 gzip 再小一截 现代浏览器都支持
identity 不压缩(默认隐含) —
* 上面没列的都可以 少见
deflate 的历史问题值得记一句:规范和早期实现不一致,有的发 zlib 包装过的数据,
有的发裸 deflate 流,客户端经常解不出来。所以现实中优先用 gzip 或 br。

二、q 值在这里怎么生效

Demo 里有一组 gzip;q=0.3, deflate;q=0.9,服务端选了 deflate。因为客户端明说更想要 deflate。 这跟很多人的直觉不同——代码里常常是先看 gzip 有没有,有就用。 正确做法是按 q 排序再挑,跟 Accept 的规则一样。 另一组 gzip;q=0 更微妙:q=0 不是「不提」,而是「明确拒绝」。 不提是「随便」,q=0 是「别给我这个」。所以服务端退到了 identity,传输 1620 字节,一点没压。

三、那条铁律:你不提我就不压

客户端一个都不提时,服务端必须发不压缩的。Demo 第一组就是这样:1620 字节原样传。 这条规矩是踩出来的。 早年有服务端「看到请求头里有 Accept-Encoding 就压」,甚至不看内容, 结果把只认明文的客户端直接打挂。所以规矩改成「不主动压」,宁可多传几个字节也不猜。 压缩比这里很夸张——1620 压到 64 字节,只占 4%。 但那是因为正文是同一句话重复六十遍,属于压缩算法最爱的输入。 真实网页的 gzip 压缩比一般在 3 到 5 倍之间,不会有 25 倍这么好看。

四、动手 Demo:六组取值,看服务端选谁

1620 字节的正文,六种 Accept-Encoding 各请求一次,服务端按 q 值排序挑第一个自己支持的, 再算出压缩后的实际字节数。

cands = [(n, q) for n, q in cands if q > 0]
cands.sort(key=lambda x: -x[1])      # 按 q 从大到小
pick = u'identity'
for n, q in cands:
    if n in (u'gzip', u'deflate'):
        pick = n
        break
body = enc(pick)
hdrs = [(u'Vary', u'Accept-Encoding')]
if pick != u'identity':
    hdrs.append((u'Content-Encoding', pick))

实跑输出:

--------------------------------------------------------------------
Accept-Encoding 与 q 值:压缩格式怎么选
--------------------------------------------------------------------
  原文 1620 字节

  【不发】
    Accept-Encoding          (没发)
    服务端选了               identity
    响应 Content-Encoding    identity
    传输字节                 1620(原 1620,100%)

  【gzip】
    Accept-Encoding          gzip
    服务端选了               gzip
    响应 Content-Encoding    gzip
    传输字节                 64(原 1620,4%)

  【gzip 加 deflate】
    Accept-Encoding          gzip, deflate
    服务端选了               gzip
    响应 Content-Encoding    gzip
    传输字节                 64(原 1620,4%)

  【只要 deflate】
    Accept-Encoding          deflate
    服务端选了               deflate
    响应 Content-Encoding    deflate
    传输字节                 52(原 1620,3%)

  【gzip 权重低】
    Accept-Encoding          gzip;q=0.3, deflate;q=0.9
    服务端选了               deflate
    响应 Content-Encoding    deflate
    传输字节                 52(原 1620,3%)

  【明确拒绝 gzip】
    Accept-Encoding          gzip;q=0, identity
    服务端选了               identity
    响应 Content-Encoding    identity
    传输字节                 1620(原 1620,100%)


    服务端选压缩格式的规则是「按 q 从大到小,挑第一个我支持的」。
    所以 `gzip;q=0.3, deflate;q=0.9` 会选 deflate,因为客户端明说更想要 deflate。
  `gzip;q=0` 是明确拒绝——注意它和「不提 gzip」不一样:
  不提是「随便」,q=0 是「别给我这个」。
  一个都不提时选 identity,也就是不压缩。
  这是历史包袱:早年有服务器一看到 Accept-Encoding 就无脑回 gzip,
  哪怕客户端没说要,结果把只认明文的客户端打挂。所以规矩变成「你不提我就不压」。

不发:identity,1620 字节,100%。服务端不主动压。 gzip:64 字节,4%。压缩 25 倍。 gzip, deflate:还是 gzip,64 字节。两个 q 都是 1,按客户端给的顺序取第一个。 只要 deflate:52 字节,3%——比 gzip 还小一点,这是 zlib 和 gzip 头部差异造成的。 gzip;q=0.3, deflate;q=0.9:选 deflate,52 字节。q 值真的生效了。 gzip;q=0, identity:identity,1620 字节。q=0 就是明确拒绝。

30 秒实验

  1. 加一个 br 的候选,看本机的 zlib 能不能造出来(不能,需第三方库)。
  2. 把正文换成一段真实 HTML,看压缩比掉到多少。
  3. 想一想:如果服务端不管客户端发什么都压,会打挂哪类客户端?

常见误区

  1. 「q 值是百分比」——是 0 到 1 的权重。
  2. 「q=0 等于没提」——是明确拒绝,比「没提」强得多。
  3. 「压缩比越高越好」——压得越狠 CPU 花得越多,小响应用 br 反而更慢。
自测三问(点开对答案) 1. `gzip;q=0` 和「不提 gzip」有什么区别? —— 前者是明确拒绝,后者是「随便,我不表态」。 2. 客户端什么都不发时,服务端该压吗? —— 不该。规矩是「不提就不压」,避免打挂只认明文的客户端。 3. `Content-Encoding` 会在什么时候出现? —— 响应正文真的被压缩时才出现,值为 gzip/deflate/br 等。

编码说完,下一个还是协商:这次协商的是语言。

相关笔记