You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)

如果需要手动实现压缩逻辑,需要注意:

  1. 先按HTTP标准解析Accept-Encoding的权重值,选择优先级最高的客户端支持编码
  2. 选中编码后用对应编码器包装ResponseWriter,不要提前手动写Content-Encoding头,包装逻辑会自动处理头写入
  3. 调用下游Handler后立刻return,不要继续遍历其他编码分支
  4. 无匹配编码时直接调用原始Handler返回未压缩内容

内容的提问来源于stack exchange,提问作者Accio

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 17:39:41