
为什么 gzip 中间件没生效?检查三个地方
Go 默认的 http.ServeMux 和 net/http 不自带响应压缩,加了中间件但响应头里没有 Content-Encoding: gzip,大概率是这三个环节漏了:
• 客户端请求头没带 Accept-Encoding: gzip(浏览器一般有,但 curl 默认不带,测试时得加 -H "Accept-Encoding: gzip")
• 中间件放在了路由注册之后(顺序错了,中间件必须 wrap handler,不是附加在 mux 上)
• 响应体太小(gzip 有默认最小长度阈值,标准库 gzip.NewWriter 不设限,但常用中间件如 gorilla/handlers.CompressHandler 默认只压缩 ≥1KB 的响应)
gorilla/handlers.CompressHandler 和手写 gzip.Writer 怎么选
用 gorilla/handlers.CompressHandler 省事,但控制粒度低;手写能按状态码、路径、内容类型精细开关。
• gorilla/handlers.CompressHandler 是黑盒 wrapper,无法跳过 404 或 302 响应的压缩,也不能排除 application/json 以外的类型
• 手写常见模式是包装 http.ResponseWriter,在 WriteHeader 后判断状态码,在 Write 时按 Content-Type 和长度决定是否启用 gzip.Writer
• 注意:手写必须实现 http.Hijacker、http.Flusher 等接口代理,否则 WebSocket 或流式响应会 panic
响应头被覆盖导致 Vary: Accept-Encoding 缺失
这是线上最隐蔽的坑——压缩生效了,但 CDN 或代理缓存把 gzip 和非 gzip 响应混存,用户偶尔收到乱码。
• gzip 响应必须带 Vary: Accept-Encoding,否则中间缓存认为“同一 URL 总是返回同样内容”
• 如果你在 handler 里手动写了 w.Header().Set("Content-Encoding", "gzip"),却忘了设 Vary,或者用了 Header().Add() 而不是 Set() 导致重复头,都可能让反向代理拒绝缓存或错误合并
• gorilla/handlers.CompressHandler 自动加 Vary,但自定义中间件里必须显式写:w.Header().Set("Vary", "Accept-Encoding")
小文件压缩反而增大体积?别盲目开全局压缩
gzip 对小于 150B 的文本(比如 {"ok":true})压缩后可能变大,HTTP/2 下还多一次 header 开销。
• 实测:120B JSON 经 gzip 后变成 180B+,且 CPU 花费不值得
• 建议设置合理阈值,例如只压缩 ≥ 500B 的响应体(gorilla/handlers.CompressHandlerLevel 支持传入自定义阈值)
• 更稳妥的做法是结合 Content-Type:只对 text/*、application/json、application/javascript 开启,跳过 image/* 和已压缩格式(application/pdf 等)
立即学习“go语言免费学习笔记(深入)”;
真正难的不是加压缩,而是确认它在所有路径、所有状态码、所有客户端条件下稳定生效——尤其是和日志中间件、认证中间件、重定向逻辑共存时,WriteHeader 调用时机稍有偏差,gzip 就会静默失效。










