gzip.handler必须置于最外层,否则因底层responsewriter被中间件封装而无法劫持write/writeheader,导致响应头含content-encoding: gzip但body未压缩,引发err_content_decoding_failed;它仅在状态码为2xx/3xx、content-length>1024或未知、且content-type可压缩(如application/json)时生效。

gzip.Handler 必须放在最外层,否则压缩静默失效——这是你启用 Gzip 后仍看到 ERR_CONTENT_DECODING_FAILED 或响应体没变小的最常见原因。
为什么 gzip.Handler 必须包在最外层?
它不是装饰器式中间件,而是直接接管原始 http.ResponseWriter 的 Write() 和 WriteHeader() 调用。一旦被日志、CORS、Auth 或路由中间件(如 chi.Router、gin.Engine)包裹在内层,底层 writer 就被封装成带额外逻辑的 wrapper,gzip.Handler 就无法劫持写入行为。
结果是:响应头里有 Content-Encoding: gzip,但 body 没压缩,浏览器解压失败。
- ✅ 正确写法:
http.ListenAndServe(":8080", gzip.Handler(myMux)) - ❌ 错误写法:
http.ListenAndServe(":8080", loggingMiddleware(gzip.Handler(myMux))) - 静态文件服务(如
http.FileServer)也必须被gzip.Handler包裹,否则.js、.css不会压缩 - 若用
chi.Router,确保gzip.Handler是最终包装者,不是chi.Chain().Use(...)里的一个环节
哪些响应会被 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等会被跳过
常见踩坑:
- 返回 JSON 却设了
Content-Type: text/plain→ MIME 类型不匹配,压缩失效 - handler 中提前调了
w.WriteHeader(200)→ header 锁定,后续没法补Content-Encoding头 - 调试时直接跑:
curl -I -H "Accept-Encoding: gzip" http://localhost:8080/,看不到Content-Encoding就说明没进压缩逻辑
手动用 gzip.Writer 时,Close() 绝对不能省
gzip.Writer.Write() 只把数据送入缓冲区,真正的压缩、CRC32 校验和、原始长度(ISIZE)都只在 Close() 时写入流末尾。漏掉它,解压端必然报 gzip: invalid checksum 或 unexpected EOF。
- 必须在 handler 返回前调用
gz.Close() - 务必在调
w.WriteHeader()前设置w.Header().Set("Content-Encoding", "gzip"),否则浏览器不解压 - 第一次
Write()时才初始化*gzip.Writer,避免对304、204等无 body 响应做无效包装 - 检查
r.Header.Get("Accept-Encoding")是否含gzip,且未被上游中间件提前消费
想排除健康检查路径或调压缩级别?得自己写 wrapper
gzip.Handler 不接受任何参数,没法加白名单、设级别、排除 /healthz。这时候只能抄它的逻辑,手动用 gzip.NewWriterLevel 包装 ResponseWriter。
- 关键点在于完整实现
http.ResponseWriter接口,尤其重写WriteHeader()—— 在这里检查是否支持 gzip 并决定是否启用压缩 - 支持
br或zstd?标准库不支持,得引入cloudflare/compress等第三方 - 流式响应(
text/event-stream、chunked)不兼容gzip.Writer,强行压缩会导致延迟激增或连接中断
真正容易被忽略的是:压缩逻辑和中间件顺序、header 设置时机、以及 Close() 的调用位置——这三个点出错,压缩就变成“看起来开了,实际没生效”。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!











