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
相关产品推荐
相关产品推荐

