概述
压缩是内容协商的结果,而不是无条件行为。客户端发送 Accept-Encoding: gzip, br,服务端才可以选择编码。若服务端强行压缩,或压缩了不该压缩的类型,不仅浪费字节,还会让共享缓存产生歧义。中间件在写出响应时运行,能看到最终的 Content-Type,因此每个响应只判断一次,实现简单且可靠。
步骤
- 安装并在路由之前注册压缩中间件,设置 1 KB 阈值:
import compression from 'compression';
app.use(compression({ threshold: 1024 }));
- 在过滤器中排除图片、视频、压缩包与字体等已压缩类型:
app.use(compression({
threshold: 1024,
filter: (req, res) => {
const type = String(res.getHeader('Content-Type') ?? '');
if (/^(image|video|audio)\//.test(type)) return false;
if (/(zip|gzip|br|woff2)$/.test(req.path)) return false;
return compression.filter(req, res);
},
}));
- 为所有可压缩响应保留
Vary: Accept-Encoding,让共享缓存按编码分别存储变体。 - 用
curl -H 'Accept-Encoding: gzip'与identity各取一次,对比size_download。
验证
同一份 JSON 在 gzip 下应从约 400 KB 降到 18 KB 左右;不带 Accept-Encoding 的请求必须返回原始字节且没有 Content-Encoding。同时确认 304 Not Modified 没有响应体、没有被压缩,SSE 接口仍然逐条增量推送。
注意事项
- 代理与应用同时压缩会导致双重编码,必须只保留一层负责
Content-Encoding。 - 压缩消耗 CPU,小响应与已压缩类型应跳过;大静态资源建议构建期预压缩交给 CDN。
- 混合了密钥与攻击者可控内容的响应可能遭受 BREACH 类攻击,这类接口应关闭压缩。
- 除了体积,还要观察 p99 延迟与 CPU 占用,避免用延迟换体积。