Content-Encoding 说明正文为了传输被压过几道、按什么顺序压的。
解码要倒着来,写反了客户端解出来就是一坨乱码。
上一节讲方法,这一节讲正文的「形态」。
它和 6.4.3 的 Accept-Encoding 是一对:一个问能不能压,一个说压了什么。
一、值和压缩顺序
常见值是 gzip、compress、deflate、identity。identity 表示没压过,通常干脆不写这个首部。
多个编码时按施加顺序从前往后列。
Content-Encoding: gzip, deflate 的意思是「先 gzip,再 deflate」,
客户端要先 deflate 解,再 gzip 解。
Demo 里实测:630 字节的原文,只 gzip 是 131 字节(20.8%),
只 deflate 是 119 字节(18.9%)。
再套一层变成 142 字节——反而比单层大,因为第二层压的是已经压过的数据,压不动还多了头。
二、它管的是「传输」,不管「文件是什么」
这句话最容易搞混。Content-Encoding: gzip 表示「传输时压过」,不代表这是个 gzip 文件。
如果服务器发的是现成的 .tar.gz,那属于文件格式,该写成 Content-Type: application/gzip,
Content-Encoding 反而不该出现——否则客户端会再解一次,把文件解坏。
判断方法很简单:把 Content-Encoding 全部逆序解完,得到的应该是「原始正文」。
如果解完还是 gzip 文件,说明你标错了位置。
三、压缩之后 Content-Length 是谁的长度
是压缩后的长度。这一条和 6.6.4 直接相关:Content-Length 数的是网线上实际发出去的字节。
所以同一份内容,压缩前后 Content-Length 完全不同。
缓存按压缩后的版本存,给不同客户端发不同的版本——正是这件事逼出了 6.5.8 的 Vary。
还有一个坑:Content-Encoding 和 Content-Range 一起用的时候,范围指的是编码后字节流的位置,
不是原文的位置。压缩流不能随便切,所以实际实现里带压缩的资源往往干脆不支持范围请求。
四、动手 Demo:630 字节原文,三种交付方式,再套两层
同一段中文正文,分别用 identity、gzip、deflate 三种方式发,量字节数; 再用 gzip 压一次、deflate 再压一次,看双重编码的首部怎么列、怎么解。
raw = TEXT * 6
gz = gzip.compress(raw, 6)
df = zlib.compress(raw, 6)
def fn(raw_req, st):
ae = hd.get(u'accept-encoding', u'')
if u'gzip' in ae:
return X.reply(u'HTTP/1.1 200 OK',
[(u'Content-Encoding', u'gzip')], gz)
return X.reply(u'HTTP/1.1 200 OK', [], raw)
# 双重编码:先 gzip,再 deflate,首部按施加顺序写
two = zlib.compress(gzip.compress(raw, 6), 6)
back = gzip.decompress(zlib.decompress(two)) # 解码倒着来
实跑输出:
--------------------------------------------------------------------
Content-Encoding:正文被怎么压过
--------------------------------------------------------------------
原文 630 字节,三种交付方式:
Content-Encoding 字节 占原文
identity(不声明) 630 100.0%
gzip 131 20.8%
deflate 119 18.9%
真发一次 gzip 的,客户端按 Content-Encoding 解压,再核对内容:
响应 255 字节,首部里 Content-Encoding: gzip
正文 131 字节,解压后 630 字节
解压结果与原文一致:True
多重编码时,Content-Encoding 按「施加顺序」从前往后列,解码要倒着来:
先 gzip 再 deflate → 声明 gzip, deflate
发出 142 字节;客户端先 deflate 解,再 gzip 解
deflate 解完 131 字节,gzip 解完 630 字节,与原文一致:True
注意两件事:Content-Encoding 是「为了传输而压」,跟文件本身是不是 .gz 无关;
文件本身的压缩叫 Content-Type 的事。另外顺序写反了,客户端解出来就是一坨乱码。
三行占比:identity 100.0%(630 字节)、gzip 20.8%(131 字节)、 deflate 18.9%(119 字节)。中文正文里重复模式多,压得动。 发一次 gzip:整条响应 255 字节,其中正文 131 字节,解压回 630 字节,与原文逐字节一致。 双重编码:先 gzip 再 deflate,总长 142 字节——比单层 gzip 的 131 还大。 先 deflate 解回 131 字节,再 gzip 解回 630 字节。 142 大于 131 这件事值得记:压第二层通常没用。 真实世界里几乎见不到双重编码,就是因为它不划算还容易写错顺序。
30 秒实验
- 把
Accept-Encoding改成br,看服务端会不会退回 identity。 - 把双重编码的首部写成
deflate, gzip(顺序反了),看解出来是什么。 - 想一想:为什么压缩过的资源一般不适合做范围请求?
常见误区
- 「Content-Encoding: gzip 表示这是个 gz 文件」——那是 Content-Type 的事。
- 「多个编码按列出的顺序解」——按顺序施加,要倒着解。
- 「套两层一定更小」——实测 142 比单层 131 还大。
自测三问(点开对答案)
1. `Content-Encoding: gzip, deflate` 该怎么解? —— 先 deflate 解,再 gzip 解——解码顺序与施加顺序相反。 2. 压缩之后的 `Content-Length` 数的是什么? —— 压缩后、实际发到网线上的字节数。 3. 为什么双重编码很少见? —— 第二层压不动已经压过的数据,实测反而从 131 涨到 142 字节。形态说完了,接下来是语言:同一份内容可能有中文版、英文版、日文版。
- ☐ (本节完,下一节 6.6.3 Content-Language)