Golang Web接口响应压缩技巧_Gzip中间件配置与性能对比

15次阅读

一个靠谱的 gzip 中间件需在 writeheader 后或首次 write 前判断 accept-encoding 并设置 content-encoding 和 vary 头,用 bytes.buffer 缓存并设阈值(如 ≥1024 字节),跳过 204/304、二进制类型、set-cookie 响应,调用 gzwriter.close(),避免手动设 content-length;brotli 可选但需注意 cdn 是否已启用。

Golang Web 接口响应压缩技巧_Gzip 中间件配置与性能对比

怎么写一个靠谱的 gzip 中间件

Go 标准库不带开箱即用的 gzip 中间件,但也不需要第三方依赖——自己写一个 30 行以内的 wrapper 就够用,关键是把 WriteHeaderWrite 的时机、顺序、头设置都卡准。

  • 必须在第一次 Write 前或 WriteHeader 后立即决定是否启用 gzip,否则响应头已发送,Content-Encoding 加不上
  • 检测请求头用 r.Header.Get("Accept-Encoding"),别用 strings.Contains 粗暴匹配,要支持 gzip, deflate, br 这种逗号分隔格式(建议用 strings.FieldsFunc(v, func(r rune) bool {return r == ',' || r == ' '}) 拆)
  • 创建 gzip.NewWriter(w) 后,立刻调用 gzWriter.SetLevel(gzip.DefaultCompression),别用 BestCompression——它会让首字节延迟飙升,API 场景完全没必要
  • 务必在 handler 返回前调用 gzWriter.Close(),漏掉这句会导致 gzip 流不完整,客户端解压失败,报错是 gzip: invalid headerinvalid compressed data--format violated

哪些响应不该压缩

盲目压缩所有响应反而拖慢服务,尤其对小数据或本就压缩过的资源,CPU 白花、体积还可能变大。

  • 跳过状态码为 204304 的响应——它们没有响应体,压缩器会 panic 或静默失败
  • 跳过 Content-Typeimage/*video/*application/pdfapplication/zip 的响应,这些格式本身已是高压缩态
  • 设置最小响应体阈值(比如 ≥1024 字节),小响应(如 {"ok":true})压缩后反而更大,且增加 GC 压力;可用 bytes.Buffer 先缓存,达到阈值再启动 gzip.Writer
  • 跳过带 Set-Cookie 头的响应——某些反向代理(如旧版 Nginx)会因 Vary 处理不当导致缓存污染,虽不是 Go 问题,但值得规避

Content-Encoding 和 Vary 头为什么不能少

这两个头不是可选项,而是 HTTP 缓存协商的契约。缺了它们,CDN、浏览器、代理层会把压缩版和未压缩版当成同一份内容缓存,结果用户拿到乱码或解压失败。

  • Content-Encoding: gzip 必须在 WriteHeader 后、首次 Write 前设置,且只能设一次;重复设置会被忽略
  • Vary: Accept-Encoding 必须同步设置,告诉所有中间层:“这个响应体是否压缩,取决于请求里的 Accept-Encoding
  • 别手动设置 Content-Length——gzip 后长度未知,Go 会自动切到 Transfer-Encoding: chunked,强行设会冲突报错

要不要上 Brotli?br > gzip 的真实收益

现代浏览器(Chrome 50+、Firefox 44+、Safari 11+)全量支持 br,压缩率比 gzip 高 15%~20%,解压更快,但 Go 标准库不内置,得引入 github.com/andybalholm/brotli

立即学习 go 语言免费学习笔记(深入)”;

  • 中间件里按 Accept-Encoding 优先级选算法:先找 br,再找 gzip,最后 fallback 到明文;别只认 gzip
  • brotli.NewWriterLevel(w, 4) 是推荐起点,比默认 11 更快,压缩率损失极小
  • 注意 brotli writer 同样要 Close(),且它的内存占用略高于 gzip,高并发小响应场景需压测验证
  • 如果已有 CDN(如 Cloudflare),确认它是否已自动开启 Brotli——重复压缩不会报错,但浪费 CPU

最常被忽略的点是:Vary 头必须随实际编码动态更新。用 br 就写 Vary: Accept-Encoding,但不能写成 Vary: Accept-Encoding, User-Agent 这种过度泛化形式,否则缓存命中率断崖下跌。

text=ZqhQzanResources