Go使用Brotli作为压缩中间件响应未压缩问题排查
问题描述
我正在编写可根据请求头Accept-Encoding对响应内容进行压缩的HTTP中间件,初始实现代码如下:
func compress(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { for _, enc := range strings.Split(r.Header.Get("Accept-Encoding"), ",") { switch strings.TrimSpace(enc) { case "br": fmt.Println("br") w.Header().Set("Content-Encoding", "br") r.Header.Del("Accept-Encoding") w.Header().Add("Vary", "Accept-Encoding") compressor := chi.NewCompressor(5, "/*") compressor.SetEncoder("br", func(w io.Writer, level int) io.Writer { params := brotli_enc.NewBrotliParams() params.SetQuality(level) return brotli_enc.NewBrotliWriter(params, w) }) compressor.Handler(h).ServeHTTP(w, r) case "gzip": fmt.Println("gzip") w.Header().Set("Content-Encoding", "gzip") r.Header.Del("Accept-Encoding") w.Header().Add("Vary", "Accept-Encoding") h.ServeHTTP(w, r) } } }) }
通过如下方式挂载中间件:
compress(handler)
实际请求时接口响应体始终未压缩,需要排查实现中的疏漏。
核心疏漏点
- 遍历
Accept-Encoding列表时未做终止处理,若请求头携带多个编码值(如gzip, br),会重复调用下游Handler、重复写入响应头,导致响应逻辑混乱。 - gzip分支仅设置了
Content-Encoding: gzip响应头,没有实际用gzip编码器包装ResponseWriter,下游直接写入原始未压缩内容,和头声明不匹配,也不会产生压缩效果。 - br分支逻辑错误:一是提前手动写入
Content-Encoding头、删除请求的Accept-Encoding头,会导致chi内置的压缩逻辑检测不到支持的编码,直接跳过压缩流程;二是每次请求都新建Compressor实例,存在不必要的性能损耗。 - 未处理
Accept-Encoding的质量参数场景:标准格式中编码值可能携带权重后缀(如br;q=0.8),仅做TrimSpace无法匹配到case "br"分支,直接不触发压缩逻辑。 - 缺失兜底逻辑:如果请求的
Accept-Encoding中没有br、gzip这两个支持的编码,遍历完所有分支后不会调用下游Handler,请求会直接挂起无响应。
修正参考
直接复用chi的Compressor能力即可,不需要自己手动处理编码头、遍历逻辑,chi会自动完成编码优先级判断、响应头写入、编码器包装的全流程,只需要额外注册br编码器即可:
// 全局初始化一次压缩器,不要每次请求新建 var compressorMiddleware = func() func(http.Handler) http.Handler { c := chi.NewCompressor(5, "/*") // 注册自定义br编码器 c.SetEncoder("br", func(w io.Writer, level int) io.Writer { params := brotli_enc.NewBrotliParams() params.SetQuality(level) return brotli_enc.NewBrotliWriter(params, w) }) return c.Handler }() // 路由挂载时直接调用即可 // r.Use(compressorMiddleware)
如果需要手动实现压缩逻辑,需要注意:
- 先按HTTP标准解析
Accept-Encoding的权重值,选择优先级最高的客户端支持编码 - 选中编码后用对应编码器包装ResponseWriter,不要提前手动写
Content-Encoding头,包装逻辑会自动处理头写入 - 调用下游Handler后立刻return,不要继续遍历其他编码分支
- 无匹配编码时直接调用原始Handler返回未压缩内容
内容的提问来源于stack exchange,提问作者Accio
相关产品推荐
相关产品推荐

