gin默认不压缩响应体,gin-contrib/gzip因仅依赖路径后缀或固定mime类型判断、不校验内容大小与content-encoding,易导致小响应膨胀、大二进制重复压缩、head请求误处理等生产问题;自研中间件需动态决策压缩时机、精确拦截content-type、适配chunked编码并合理配置gzip.writer参数。

直接上结论:Gin 默认不压缩响应体,gin-contrib/gzip 能用但不够灵活,真正可控的压缩必须自己写中间件或选型更细粒度的方案。
为什么 gin-contrib/gzip 在生产中容易踩坑
它靠路径后缀(如 .json)或固定 Content-Type 列表判断是否压缩,不看实际内容大小——导致小 JSON(比如 300 字节)也被 gzip,反而膨胀;也拦不住大 JPEG(本身已压缩)被强行再压一遍,白耗 CPU。
- 不检查
Content-Length,而 Go 的http.ResponseWriter在响应体 >2KB 时默认用 chunked 编码,Content-Length就没了 - 无法跳过已带
Content-Encoding: gzip的上游响应(比如反向代理转发来的) - 对
HEAD请求没做拦截,会错误地包装一个空 gzip body
自己写中间件时必须处理的三个关键点
核心逻辑不是“套个 gzip.Writer 就完事”,而是动态决策:该不该压、什么时候开始压、怎么避免破坏 HTTP 语义。
- 先检查
c.Request.Method是否为HEAD,是则直接Next()不干预 - 读取原始
Content-Type,只对application/json、text/plain、text/html等文本类型启用压缩,跳过image/jpeg、application/pdf - 若响应头有
Content-Length,直接比阈值(如 1024);若为空(chunked 场景),需劫持Write([]byte)第一次调用的len(data)做判断
gzip.Writer 初始化时的级别和缓冲区陷阱
gzip.NewWriterLevel(w, level) 的 level 参数不是越大越好:级别 9 压缩率高但 CPU 翻倍,级别 1 吞吐快但收益低。实测在 API 场景下,gzip.BestSpeed(即 1)和 gzip.DefaultCompression(即 6)之间折中选 4 最稳。
- 别用
gzip.NewWriter(w)默认构造——它内部缓冲区只有 4KB,小响应频繁 flush,不如显式传bufio.NewWriterSize(w, 32*1024) - 务必在
WriteHeader后、Write前设置Content-Encoding: gzip和清除Content-Length(因为 chunked 下长度不可知) - 中间件里
defer gz.Close()必须配对gz.Write(),否则部分数据滞留在缓冲区不发出
最易被忽略的是 chunked 场景下的首次 Write 观察——很多自研中间件只查 Content-Length,结果大 JSON 响应完全没进压缩流程,还以为配置错了。











