gin 默认不开启 gzip 压缩是为避免与 nginx 等反向代理重复压缩导致客户端解压失败;必须使用维护中的 gin-contrib/gzip,挂载于路由注册前,配置 bestspeed 级别、minsize 阈值及路径/类型排除规则,并通过 magic bytes 或 curl 验证真实压缩生效。

Gin 默认不开启 Gzip 压缩,不是漏配,而是防止和 Nginx 等反向代理重复压缩——一旦 Content-Encoding: gzip 被套两层,客户端解压失败,返回空白或二进制乱码(比如你看到的 pong 1468862456?n????)。
用 gin-contrib/gzip 中间件正确挂载
别用已归档的 gin-gonic/contrib/gzip,它缺失 WriteString() 实现,导致 c.String() 响应绕过压缩;改用当前维护的官方包:
go get github.com/gin-contrib/gzip
中间件必须在 r.GET()、r.POST() 等路由注册前调用,否则对所有动态接口无效:
package main
import (
"github.com/gin-contrib/gzip"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.Use(gzip.Gzip(gzip.BestSpeed, gzip.WithMinSize(1024)))
r.GET("/api/data", func(c *gin.Context) {
c.JSON(200, gin.H{"ok": true})
})
r.Run(":8080")
}
-
gzip.BestSpeed(级别 1)比DefaultCompression(级别 6)CPU 开销低 40%+,对 JSON/HTML 压缩率仅低 5%~8% -
WithMinSize(1024)跳过小于 1KB 的响应,避免压缩头开销反超收益 - 中间件挂载顺序错误是常见原因:必须写在
r.GET()之前,且不能放在r.Static()后面(静态文件不走中间件链)
排除不该压的路径和资源类型
PDF、ZIP、AVIF、WebP 等本身已高压缩,再 gzip 可能增大体积;监控接口如 /health、/metrics 的响应若被压缩,Prometheus 客户端会直接丢弃指标。
用 WithExcludedPaths 和 WithExcludedPathsRegexp 显式过滤:
r.Use(gzip.Gzip(
gzip.BestSpeed,
gzip.WithMinSize(1024),
gzip.WithExcludedPaths([]string{"/health", "/metrics"}),
gzip.WithExcludedPathsRegexp("^/.*\.(pdf|zip|avif|webp|jpg|png|mp4)$"),
))
- 正则中
\.是转义点号,$锚定结尾,避免误伤/api/v1/pdf-report这类路径 - 注意
r.Static("/static", "./assets")返回的文件不经过该中间件——如需压缩静态资源,得换用gzipfs配合r.StaticFS(),或构建时预生成.gz文件由 Nginx 直接 serve
验证压缩是否真正生效
别只看响应头有没有 Content-Encoding: gzip——很多中间件会错误地加了头但没真正压缩内容,导致客户端解压失败。
用 curl 显式声明支持并检查原始字节:
curl -H "Accept-Encoding: gzip" -I https://www.php.cn/link/b90b68a89274ab9b23e1c36872ca04ac
# 应看到:Content-Encoding: gzip + Vary: Accept-Encoding
<p>curl -H "Accept-Encoding: gzip" <a href="https://www.php.cn/link/b90b68a89274ab9b23e1c36872ca04ac">https://www.php.cn/link/b90b68a89274ab9b23e1c36872ca04ac</a> | hexdump -C | head -5</p><h1>正常压缩响应开头是 1f 8b(gzip magic bytes),不是 7b 7d(JSON 的 { } ASCII)</h1>
- 如果
hexdump输出以1f 8b开头,说明真压缩了;如果是7b 22({"),说明中间件没生效或被跳过 - 浏览器 DevTools 的 Network 面板里,Size 列显示 “transferred” 小于 “resource” 才算有效压缩;若两者相等,大概率只是加了头没压
- 客户端必须带
Accept-Encoding: gzip请求头,否则中间件自动跳过——前端发请求时默认不带,需显式配置 fetch / axios headers
真正难的不是加一行 r.Use(gzip.Gzip(...)),而是判断哪些响应值得压、哪些必须跳过、以及如何和前置代理协同——Nginx 已开 gzip on 时,Gin 层就该关掉,否则双压风险极高。生产环境优先让 Nginx 做压缩,Gin 层只在直连内网或调试阶段启用。











