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

Go包装ResponseWriter后响应JSON解码与缓冲区污染处理问题

问题:包装ResponseWriter后缓冲区数据被编码污染,无法解析为JSON日志

我按照StackOverflow的建议包装了ResponseWriter,代码如下:

type MyResponseWriter struct {
    http.ResponseWriter
    buf *bytes.Buffer
}

func (mrw *MyResponseWriter) Write(p []byte) (int, error) {
    return mrw.buf.Write(p)
}

但缓冲区buf被编码数据污染,长请求里得到的是完全不可读的内容,推测和压缩有关。现在想把这些数据解析成JSON用于日志记录,该怎么处理?


解决思路

核心问题是拦截的响应数据已被压缩(如gzip)或编码,直接读取缓冲区得到的是二进制编码内容,而非原始JSON。以下是两种处理方案:

1. 临时禁用响应压缩(测试场景)

如果仅用于测试或低流量场景,可移除请求头的Accept-Encoding,让后端返回未压缩内容:

func TestLoggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // 移除Accept-Encoding头,避免后端返回压缩响应
        r.Header.Del("Accept-Encoding")
        
        buf := &bytes.Buffer{}
        mrw := &MyResponseWriter{w, buf}
        next.ServeHTTP(mrw, r)
        
        // 缓冲区内容为原始未压缩JSON
        log.Println("响应内容:", buf.String())
    })
}

2. 解码压缩响应(生产推荐)

生产环境需保留压缩,需根据响应头的Content-Encoding解码缓冲区内容:

首先完善MyResponseWriter,修复未向客户端返回数据的bug,并记录响应状态和头部:

import (
    "bytes"
    "net/http"
)

type MyResponseWriter struct {
    http.ResponseWriter
    buf        *bytes.Buffer
    statusCode int
}

// 记录响应状态码
func (mrw *MyResponseWriter) WriteHeader(code int) {
    mrw.statusCode = code
    mrw.ResponseWriter.WriteHeader(code)
}

// 同时向客户端和缓冲区写入数据
func (mrw *MyResponseWriter) Write(p []byte) (int, error) {
    // 先将数据返回给客户端
    n, err := mrw.ResponseWriter.Write(p)
    if err != nil {
        return n, err
    }
    // 再写入缓冲区
    return mrw.buf.Write(p[:n])
}

然后在中间件中根据编码类型解码:

import (
    "compress/flate"
    "compress/gzip"
    "encoding/json"
    "io"
    "log"
    "net/http"
)

func ProductionLoggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        buf := &bytes.Buffer{}
        mrw := &MyResponseWriter{
            ResponseWriter: w,
            buf:            buf,
            statusCode:     http.StatusOK,
        }
        
        next.ServeHTTP(mrw, r)
        
        // 根据响应头的编码类型解码
        var responseBody []byte
        encoding := mrw.Header().Get("Content-Encoding")
        
        switch encoding {
        case "gzip":
            gr, err := gzip.NewReader(buf)
            if err != nil {
                log.Printf("gzip解码失败: %v", err)
                return
            }
            defer gr.Close()
            responseBody, _ = io.ReadAll(gr)
        case "deflate":
            dr := flate.NewReader(buf)
            defer dr.Close()
            responseBody, _ = io.ReadAll(dr)
        default:
            // 无编码,直接读取缓冲区
            responseBody = buf.Bytes()
        }
        
        // 解析并记录JSON
        var jsonData map[string]interface{}
        if err := json.Unmarshal(responseBody, &jsonData); err == nil {
            log.Printf("响应状态码: %d, JSON内容: %+v", mrw.statusCode, jsonData)
        } else {
            log.Printf("响应状态码: %d, 非JSON内容: %s", mrw.statusCode, string(responseBody))
        }
    })
}

关键注意事项

  • 原代码的Write方法仅写入缓冲区,未向客户端返回数据,这是严重bug,必须先调用mrw.ResponseWriter.Write(p)。
  • 需处理常见的压缩类型(gzip、deflate),若有其他编码(如br),需对应添加解码逻辑。
  • 生产环境要捕获解码和JSON解析的异常,避免程序崩溃。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 09:53:20