Go微服务需手动启用HTTP响应压缩,gzip.Handler必须置于最外层且满足三条件:2xx/3xx状态码、Content-Length未知或>1024字节、Content-Type在可压缩列表中;流式响应不可压缩,中间件错位会静默失效。

Go微服务默认不压缩HTTP响应,哪怕客户端明确支持gzip,你也得手动加一层压缩逻辑,否则带宽浪费、首屏变慢是实打实的问题。
用 gzip.Handler 是最稳的起点,但必须放最外层
标准库自带的 gzip.Handler 不是装饰器,它是“接管者”——必须包在你整个路由处理器外面,否则它压不到响应体。
- ✅ 正确:
http.ListenAndServe(":8080", gzip.Handler(myMux)) - ❌ 错误:
http.ListenAndServe(":8080", loggingMiddleware(gzip.Handler(myMux)))(日志中间件已封装ResponseWriter,gzip.Handler失效) - 静态文件也要包进去,比如
http.FileServer,不然.js、.css不压缩 - 它自动删掉
Content-Length,改用Transfer-Encoding: chunked,别自己再设Content-Length
gzip.Handler 什么情况下会静默跳过压缩
它不是“有 Accept-Encoding: gzip 就压”,而是硬性三条件缺一不可:
- 状态码必须是
2xx或3xx(404、500默认不压) -
Content-Length未知,或已知且 > 1024 字节(小响应不压) -
Content-Type必须在可压缩列表里,比如text/html、application/json;image/png、application/octet-stream被跳过 - 调试时直接用
curl -I -H "Accept-Encoding: gzip" http://localhost:8080/,看有没有Content-Encoding: gzip响应头
手动用 gzip.Writer 时,Close() 绝对不能省
Write() 只压数据进缓冲区,真正的 CRC32 校验和、原始长度(ISIZE)只在 Close() 时写入流末尾。漏掉就解压失败。
- 错误写法:
gz.Write(data)后没gz.Close()→gzip: invalid checksum或unexpected EOF - 别在
Close()前调buf.Bytes(),此时 buffer 还没 flush 完整 - 如果后续要把压缩结果存 Redis(如
HSET cache:key body),buf.Bytes()必须在gz.Close()之后取 - 压缩前务必检查状态码是否为
2xx,且Header().Get("Content-Encoding") == "",避免重复压缩
流式响应(SSE / chunked)不能套 gzip.Writer
text/event-stream 或长轮询这类接口,要求低延迟分块输出,而 gzip.Writer 需要完整输入或显式 Flush(),强行压缩会导致卡顿甚至连接中断。
- 解决方式只有两个:对这类路径禁用压缩,或改用反向代理(如 Nginx)做压缩
- 若必须手写中间件,需在
WriteHeader()前判断r.Header.Get("Accept-Encoding")是否含gzip,且r.URL.Path不匹配流式路径(如/events) -
Content-Encoding: gzip和流式传输本身不兼容,浏览器和客户端 SDK 通常也不期望流式响应被压缩
真正容易被忽略的是:压缩中间件一旦嵌套错位,失效是静默的——没有报错,只是响应没压缩,你得靠 curl -I 或抓包确认 Content-Encoding 头是否存在。


















