ctx.writer.size()不能直接使用,因为其返回值在c.json()调用后可能为-1或不准确,尤其在启用gzip、streaming响应或中间件提前写入时;需自定义sizeresponsewriter包装器,在write()中累加字节数,并确保注册顺序在gzip等中间件之前。

为什么 ctx.Writer.Size() 不能直接用
因为 Gin 的 ctx.Writer 在调用 c.JSON() 后才真正写入响应体,但此时 Size() 返回的可能是 -1(未缓冲)或不准确值——尤其启用了 gzip 中间件、使用了 streaming response(如 c.Stream())、或中间件提前写了部分响应时。Size() 本质依赖底层 http.ResponseWriter 是否实现了 ResponseWriter 接口的 Size() 方法,而标准库没实现,Gin 的 responseWriter 仅在启用 gin.Recovery() 或显式包装时才维护字节数。
推荐方案:用 gin.ResponseWriter 包装并监听 Write()
最可靠的方式是自定义一个 wrapper,拦截所有 Write() 调用并累加字节数。注意:必须在所有中间件(尤其是压缩、日志类)之前注册,否则会漏统计中间件产生的额外输出。
- 新建一个结构体实现
http.ResponseWriter和gin.ResponseWriter接口 - 重写
Write([]byte),在调用原Write()前累加长度 - 重写
WriteHeader(int),确保状态码被正确传递 - 注意:不要重写
WriteString(string)—— Gin 的JSON()底层走的是Write(),不是WriteString()
示例片段:
type SizeResponseWriter struct {
gin.ResponseWriter
size int
}
func (w *SizeResponseWriter) Write(b []byte) (int, error) {
n, err := w.ResponseWriter.Write(b)
w.size += n
return n, err
}
func (w *SizeResponseWriter) Size() int {
return w.size
}
在中间件中注入并记录 JSON 字节数
关键点:必须在 c.Next() 之后读取 size,且只对 Content-Type: application/json 的响应生效(避免统计 HTML、404 页面等干扰)。
在 Go 中使用 google/wire 实现编译时依赖注入——wire.NewSet、wire.Build、wire.Bind(接口→实现)、wire.Struct、wire.Value、wire.Interface
- 检查
w.Header().Get("Content-Type")是否包含"application/json" - 避免在 panic 恢复路径里重复统计(
Recovery()中间件可能已写入错误 JSON) - 如果用了
gin.Logger(),它默认不打印 body size,需自行扩展日志字段 - 性能影响极小,单次
Write()额外一次整数加法,无内存分配
典型中间件写法:
func ResponseSizeMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
w := &SizeResponseWriter{
ResponseWriter: c.Writer,
size: 0,
}
c.Writer = w
c.Next()
if ct := c.Writer.Header().Get("Content-Type"); strings.Contains(ct, "application/json") {
log.Printf("path=%s status=%d size=%d", c.Request.URL.Path, c.Writer.Status(), w.Size())
}
}
}
别踩 gzip 压缩的坑
如果启用了 gin-contrib/gzip,原始 JSON 字节数会被压缩逻辑覆盖——你拿到的是压缩后大小,不是 JSON 原始字节数。要统计“API 返回的 JSON 字节数”,必须在 gzip 中间件之前插入 size 统计中间件。
- 注册顺序必须是:
SizeMiddleware→gzip.Gzip(gzip.DefaultCompression)→your handlers - 否则
Write()被 gzip wrapper 拦截,你统计的是压缩流字节,不是 JSON 原文 - 若需同时知道压缩前/后大小,得在 gzip wrapper 内部再做一层 hook,代价高,通常没必要
真实场景里,多数监控和计费依据的是网络传输字节数(即压缩后),但“JSON 字节数”语义明确指序列化后的原始 payload —— 这个细节容易被忽略,得看清楚需求到底要什么。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!










