Beego中GZIP中间件必须用自定义ResponseWriter wrapper缓存响应体,待handler执行完再判断Accept-Encoding、Content-Type、响应大小等条件决定是否压缩,并确保Content-Encoding和Vary头在WriteHeader后、首次Write前正确设置,否则会因响应头提前发送导致压缩失效或协议错误。

Beego 中间件无法直接包装 ResponseWriter 做 GZIP
因为 Beego 的 http.ResponseWriter 在 WriteHeader 或首次 Write 时就已锁定并发送响应头,而 GZIP 中间件必须在写入前设置 Content-Encoding: gzip 和 Vary: Accept-Encoding。若在中间件里直接用 gzip.NewWriter(w) 包裹原始 w,handler 写入时会提前发头,导致压缩头被忽略或 HTTP 协议错误。
必须用 wrapper 缓存响应体再统一压缩
正确做法是实现一个自定义 ResponseWriter wrapper,内部用 bytes.Buffer 暂存响应内容,等 handler 执行完、状态码和 header 都确定后再决定是否启用 GZIP 并写出。关键点包括:
-
Header()方法必须返回原始w.Header(),否则c.Ctx.Output.Header().Set(...)失效 - 首次
Write()前未调用WriteHeader(),需补http.StatusOK - 仅当
r.Header.Get("Accept-Encoding")包含gzip(注意解析逗号分隔值,别用strings.Contains)、响应体 ≥1024 字节、且Content-Type不是image/*、video/*、application/pdf等二进制类型时才启用压缩 - 压缩前必须调用
gzWriter.Close(),漏掉会导致客户端解压失败,报错如gzip: invalid header
Beego 中注册 GZIP 中间件要选对 FilterType
Beego 的中间件注册不支持通配符或正则,但可通过路径前缀精确控制作用范围。例如只对 API 响应启用 GZIP:
- 用
beego.InsertFilter("/api/", beego.BeeApp.FilterType, gzipMiddleware)—— 注意结尾斜杠,避免误匹配/apixxx - 注册必须在
beego.Run()之前,通常放在main()开头或init()中 - 不要用
beego.BeforeRouter或beego.BeforExec,它们对静态资源无效;若需压缩/static/下的文本资源(如 JS/CSS),改用beego.BeforeStatic - 静态资源中间件中
c.Data为空,只能依赖c.Ctx.Request和c.Ctx.ResponseWriter
容易被忽略的 HTTP 头与状态码陷阱
GZIP 不只是“把 body 压一下”,它是一套缓存协商契约。漏掉以下任一环节,CDN 或浏览器可能缓存错乱:
-
Content-Encoding: gzip必须在WriteHeader()后、首次Write()前设置,且不能重复设 -
Vary: Accept-Encoding必须同步设置,否则代理层会把压缩版和未压缩版当成同一缓存键 - 跳过状态码为
204、304的响应——它们无 body,gzip.Writer会 panic 或静默失败 - 跳过带
Set-Cookie头的响应——旧版 Nginx 等反向代理因Vary处理缺陷可能导致缓存污染 - 别手动设
Content-Length:GZIP 后长度未知,Go 会自动用 chunked transfer encoding
Beego 自身不提供开箱即用的 GZIP 中间件,但也不需要引入第三方包。真正难的不是写那几十行 wrapper,而是时机判断:header 是否已发、body 是否够大、content-type 是否可压、client 是否真想要——这些条件任何一个出错,压缩就从优化变成故障源。











