不喜欢广告? 无广告 今天

gzip,Brotli,Zstd HTTP 压缩:开发者无意中设置了 content-encoding: identity 的情况

更新于

HTTP 压缩协商的工作原理(Accept-Encoding / Content-Encoding),gzip、Brotli 和 Zstd 的并列性能对比,使用 curl 验证压缩是否真正生效,以及四种静默禁用压缩的错误配置。

gzip,Brotli,Zstd:HTTP压缩:开发者无意中设置了content-encoding: identity 1
广告 移除?

您的 nginx 配置包含 gzip on;。您的应用程序返回 JSON。响应体仍为 35KB 未压缩。没有错误,没有警告——压缩只是默默地没有发生。

这通常是四种配置错误之一。但首先:协商机制是如何工作的。

HTTP 压缩协商机制

两个头部,除此之外没有其他内容。客户端在 Accept-Encoding中宣布它能解压的内容。 Content-Encoding:

GET /api/data HTTP/1.1
Host: example.com
Accept-Encoding: gzip, deflate, br, zstd
HTTP/1.1 200 OK
Content-Type: application/json
Content-Encoding: br
Vary: Accept-Encoding

Vary: Accept-Encoding header 是非可选的,如果您关心 CDN 缓存的正确性。如果没有它,CDN 可能会缓存一个 Brotli 压缩的响应,并将其提供给仅宣布了 gzipAccept-Encoding的客户端。 gzip_vary on; 。该客户端随后尝试将 Brotli 解压为 gzip,结果得到垃圾数据。nginx 会自动添加这个头部。

Content-Encoding: identity 设置为“无编码”在技术上是有效的,但没有人显式设置它。实际的故障模式恰恰相反:当您期望看到它时,根本没有这个 header。 Content-Encoding header 是非可选的,如果您关心 CDN 缓存的正确性。如果没有它,CDN 可能会缓存一个 Brotli 压缩的响应,并将其提供给仅宣布了

验证压缩是否真正生效

在调试配置之前,请确认问题:

# Check headers only
curl -sI -H "Accept-Encoding: gzip, br, zstd" https://example.com/api/data   | grep -i "content-encoding\|vary"

# Compare compressed vs uncompressed size
curl -so /dev/null -w "uncompressed: %{size_download} bytes
" https://example.com/api/data
curl -so /dev/null --compressed -w "compressed:   %{size_download} bytes
" https://example.com/api/data

--compressed 会自动发送 Accept-Encoding: deflate, gzip, br, zstd 并解压响应。如果两个数值匹配,说明压缩功能未启动。如果您想检查所有响应头部及其在上下文中的含义,可以使用 HTTP 标头分析器 来注释这些头部,包括 Vary, Content-Encoding和 cache-control 指令。

gzip 与 Brotli 与 Zstd

目前对 HTTP 实际上相关的主要算法有三种。以下基准数据来自 Zstd 官方基准测试 在 Silesia 数据集上的结果——这是一个混合现实世界文件(HTML、源代码、PDF、数据库)的标准数据集,在 Core i7-9700K 上测试。纯 JSON 或纯文本负载通常压缩效果更好。

Algorithm等级比值压缩解压缩
gzip1(快速)2.74x69 MB/s380 MB/s
gzip6(默认)2.97x29.9 MB/s360 MB/s
gzip9(最大)3.10x18 MB/s360 MB/s
Brotli43.18x104 MB/s440 MB/s
Brotli11(最大)3.74x0.4 MB/s440 MB/s
Zstd1(快速)2.88x430 MB/s1,380 MB/s
Zstd3(默认)3.01x320 MB/s1,350 MB/s
Zstd19(最大)3.40x17.5 MB/s1,380 MB/s

gzip 是基准。级别 6 是实时处理的最佳选择——从级别 6 到级别 9 每增加 65% 的 CPU 成本,性能提升约 4%,对于动态响应来说不值得。静态文件的预压缩是另一种计算方式。

Brotli 在 CPU 成本相当的情况下,级别 4-6 优于 gzip,且解压速度约为 20% 更快。原因在于 Brotli 拥有一个针对网络内容优化的静态字典——HTML 实体、HTTP 字段名称、JavaScript 关键字。它在相同材料上比通用压缩器获得更好的压缩比。级别 11 仅适用于预压缩的静态资产;在 0.4 MB/s 的压缩速度下,每分钟只能压缩约 25MB。这属于构建步骤,而非请求处理器。

Zstd 是速度故事。默认级别(3)与 gzip 的压缩比相同,但压缩速度是 gzip 的 10 倍,解压速度几乎快 4 倍。主要限制是浏览器支持:Chrome 118+(2023 年 10 月),Firefox 126+(2024 年 5 月),Safari 18+(2024 年末)。目前尚不足以作为唯一算法使用,但如果服务器配置得当,添加 Zstd 仅需几行配置,即可帮助那些声明支持的客户端。Zstd 在级别 19 时接近 Brotli-11 的压缩比,而无需承受灾难性的压缩速度惩罚,使其在高压缩需求的实时处理中更具实用性。

浏览器和客户端支持

Algorithm铬合金FirefoxSafari边缘Node.js
gzip全部全部全部全部内置(zlib)
deflate全部全部全部全部内置(zlib)
Brotli (br)51+44+11+15+v10.16+
Zstd118+126+18+118+v21+

一个重要的特性: brzstd 仅在 Accept-Encoding HTTPS 连接中出现。浏览器故意不通过普通 HTTP 传输这些头部——这是为了防止中间人攻击注入编码头部。如果您在 http://localhost 上测试并发现只看到 gzip, deflate,这就是原因。请通过 HTTPS 测试,或直接使用 curl(curl 不会应用此限制)。

四种静默破坏其功能的配置错误

1. 缺少 gzip_proxied(nginx 反向代理)

nginx 的 gzip 模块会压缩其自身生成的响应。对于代理请求(上游应用到 nginx 再到客户端),您需要 gzip_proxied ——否则 nginx 只会压缩其自身内容处理器生成的响应,而不会压缩来自 proxy_pass 上游的响应。

# This is NOT enough when nginx is a reverse proxy:
gzip on;
gzip_types text/plain application/json application/javascript text/css;

# You need this too:
gzip_proxied any;

大多数 nginx 配置是反向代理。大多数教程忽略了 gzip_proxied。这两个事实解释了大量未压缩响应的问题。

2. MIME 类型未包含在 gzip_types 中

nginx 的默认 gzip_typestext/html 仅包含。JSON、CSS、JavaScript、SVG——除非明确列出,否则均不压缩:

gzip_types
  text/plain
  text/css
  text/xml
  text/javascript
  application/json
  application/javascript
  application/xml
  application/rss+xml
  image/svg+xml;

nginx 根据基础 MIME 类型进行匹配,因此 application/json 覆盖了 application/json; charset=utf-8。无需单独列出字符集变体。

3. 中间代理剥离了 Accept-Encoding

AWS ALB、配置错误的 Cloudflare Workers 和某些 API 网关设置会在响应到达源之前剥离或重写 Accept-Encoding 。服务器从未看到该头部,因此默认不进行压缩,所有下游组件都认为该功能已损坏,而实际问题出在中间件。整个链路中没有任何错误提示。

通过比较源响应与 CDN 响应来调试:

# Via CDN/proxy
curl -sI -H "Accept-Encoding: gzip, br" https://example.com/api/data

# Direct to origin (bypassing CDN via --resolve or direct IP)
curl -sI -H "Accept-Encoding: gzip, br" --resolve "example.com:443:ORIGIN_IP" https://example.com/api/data

如果源直接返回 Content-Encoding: gzip ,但 CDN 响应中没有 Content-Encoding,则 CDN 正在剥离某些内容——更可能是剥离了 Accept-Encoding 以使源在最初阶段就无法压缩。

4. 上游应用已压缩,nginx 再次尝试压缩

如果您的 Node.js/Go/Python 应用已压缩响应体并设置了 Content-Encoding: gzip,nginx 应跳过双重压缩——但这取决于头部的发送时机。如果上游在流传输中发送头部,或 nginx 的检测发生竞争,您可能会得到双重压缩的垃圾数据,客户端无法解码。

解决方案:让 nginx 全权负责压缩。从您的应用中移除压缩中间件(如 express 的 compression 模块,Go 的 gzip.Handler等),返回原始响应,由 nginx 在边缘进行压缩。获得相同的性能提升,且无双重压缩风险。

有效配置

nginx

gzip on;
gzip_vary on;         # adds Vary: Accept-Encoding automatically
gzip_proxied any;     # compress responses from proxied upstreams
gzip_comp_level 6;
gzip_min_length 256;  # skip tiny responses where overhead isn't worth it
gzip_types
  text/plain
  text/css
  text/xml
  text/javascript
  application/json
  application/javascript
  application/xml
  application/rss+xml
  image/svg+xml;

# Brotli requires the ngx_brotli module
# https://github.com/google/ngx_brotli
brotli on;
brotli_comp_level 4;
brotli_static on;     # serve pre-compressed .br files when they exist
brotli_types
  text/plain
  text/css
  application/json
  application/javascript
  image/svg+xml;

Apache

LoadModule deflate_module modules/mod_deflate.so
AddOutputFilterByType DEFLATE text/html text/plain text/css   application/json application/javascript image/svg+xml
Header append Vary Accept-Encoding

# mod_brotli requires Apache 2.4.26+
LoadModule brotli_module modules/mod_brotli.so
AddOutputFilterByType BROTLI_COMPRESS text/html text/plain text/css   application/json application/javascript image/svg+xml

Caddy

Caddy 默认启用 gzip 和 Brotli。要显式添加 Zstd:

example.com {
  encode gzip zstd br
  reverse_proxy localhost:3000
}

无需 MIME 类型列表,无需 gzip_proxied 边缘情况,正确处理 Vary 问题。对于“哪个服务器的压缩相关配置错误表面面积最小”的问题,诚实的答案是 Caddy。

在您自己的负载上测试压缩

Silesia 数据集上的基准数字只能说明相对性能,但您的具体负载更为重要。一个具有稳定字段名称的重复 JSON API 响应与最小化 JavaScript 或混合 HTML 的压缩效果不同。这些工具允许您在浏览器中测试特定负载,而无需启动本地压缩服务器:

在决定是否在 Brotli-11 下预压缩静态资产,还是让 nginx 实时处理 gzip 时非常有用。粘贴您的实际响应负载,比较比率,用真实数据做出决策。

总结

如果响应未被压缩且 curl -sI 确认没有 Content-Encoding,修复几乎肯定是上述四种配置错误之一——最可能是 gzip_proxied any; 的 nginx,或 CDN 吃掉了您的 Accept-Encoding 头部。在责备服务器配置之前,请直接检查源响应。

对于算法选择:动态 API 响应使用 gzip-6 是合适的,且配置风险几乎为零。为静态资产添加 Brotli——在构建步骤中预压缩至级别 11,通过 brotli_static on提供服务,并让 nginx 为不声明 br的客户端回退到 gzip。现在添加 Zstd 是值得的;配置成本微乎其微,且其浏览器支持正在快速增长。为新项目提供 gzip、Brotli 和 Zstd 三种算法,并正确处理 Vary: Accept-Encoding 是正确的做法。

想要享受无广告的体验吗? 立即无广告

安装我们的扩展

将 IO 工具添加到您最喜欢的浏览器,以便即时访问和更快地搜索

添加 Chrome 扩展程序 添加 边缘延伸 添加 Firefox 扩展 添加 Opera 扩展

记分板已到达!

记分板 是一种有趣的跟踪您游戏的方式,所有数据都存储在您的浏览器中。更多功能即将推出!

广告 移除?
广告 移除?
广告 移除?

新闻角 包含技术亮点

参与其中

帮助我们继续提供有价值的免费工具

给我买杯咖啡
广告 移除?