gzip.handler必须置于中间件链最外层,否则因无法劫持write/writeheader导致压缩失效;仅对2xx/3xx状态码、content-length>1024或未知、且content-type可压缩(如application/json)的响应生效。

gzip.Handler 必须包在最外层,否则压缩失效
Go 的 gzip.Handler 不是普通中间件,它依赖直接操作原始 http.ResponseWriter 的 Write() 和 WriteHeader() 方法。一旦被日志、CORS、Auth 或任何封装了 writer 的中间件包裹,它就收不到底层写入调用,导致压缩逻辑完全跳过——浏览器收到 Content-Encoding: gzip 头,但响应体未压缩,解压失败报 ERR_CONTENT_DECODING_FAILED。
- 正确写法只有一种:
http.ListenAndServe(":8080", gzip.Handler(myMux)) - 若用
chi.Router,确保gzip.Handler是最终包装者,不是chi.Use(...)里加的 - 静态文件服务(如
http.FileServer)也必须被gzip.Handler包裹,否则.js、.css不会压缩
哪些响应会被 gzip.Handler 自动跳过?
它不是“有 Accept-Encoding: gzip 就压”,而是三条件同时满足才触发压缩:
- 状态码必须是 2xx 或 3xx(4xx/5xx 默认不压,避免暴露调试细节)
-
Content-Length已知且 > 1024 字节(小响应压缩收益低,还白耗 CPU) -
Content-Type属于可压缩类型,例如text/html、application/json、text/css;而image/png、application/octet-stream这类本身已压缩的类型会被跳过
常见现象:curl -I -H "Accept-Encoding: gzip" 看不到 Content-Encoding 头,大概率是返回 JSON 却设了 text/plain,或 handler 提前调了 w.WriteHeader(200) 锁定 header 导致无法补头。
想排除健康检查路径或调压缩级别?得自己写 wrapper
gzip.Handler 不接受任何参数,没法加白名单、设级别、排除 /healthz。这时候只能抄它的逻辑,手动用 gzip.NewWriterLevel 包装 ResponseWriter:
- 必须实现完整
http.ResponseWriter接口,尤其重写WriteHeader()—— 在这里检查r.Header.Get("Accept-Encoding")是否含gzip,并决定是否启用压缩 -
Write()第一次调用时才初始化*gzip.Writer,避免对304/204等无 body 响应做无效包装 - 压缩级别可用
gzip.BestSpeed(1)或gzip.BestCompression(9),HTTP 响应推荐gzip.DefaultCompression(-1),平衡速度与压缩比
流式传输大文件时别用 bytes.Buffer
用 gzip.NewWriter 写入 bytes.Buffer 会累积全部压缩后数据在内存,大文件直接 OOM。真正高效的做法是让压缩器直接写入目标输出流:
- 服务端传文件:用
io.Copy(gzip.NewWriter(w), file),边读边压边发,内存恒定 - 客户端接收:先看响应头
Content-Encoding,是gzip就用gzip.NewReader(resp.Body)包一层再读 - 多文件打包:先用
archive/tar打包,再套gzip.NewWriter,生成.tar.gz流,避免临时文件
复杂点在于错误传播和资源释放时机:所有 defer gz.Close() 必须在 io.Copy 后,否则可能丢尾部校验;gzip.Writer 的 Close() 不仅刷新缓冲区,还写入 gzip trailer,漏掉会导致客户端解压失败。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











