c.GetRawData() 会消耗 Request.Body 导致后续 BindJSON 失败,因其内部调用 io.ReadAll 使 io.ReadCloser 到达 EOF;需用 ShouldBindBodyWith 缓存 body 或手动重置 Body 才能复用。

直接用 c.GetRawData() 就能拿到原始 JSON 字节流,但必须注意:它会消耗 c.Request.Body,后续再调用 c.ShouldBindJSON() 或其他绑定方法会失败。
为什么 c.GetRawData() 之后不能再用 BindJSON
c.GetRawData() 内部调用了 io.ReadAll(c.Request.Body),而 http.Request.Body 是一次性读取的 io.ReadCloser。读完就 EOF,后续任何绑定(如 c.BindJSON()、c.ShouldBindJSON())都会报 invalid character '}' after top-level value 或空 body 错误。
- 这是 Gin 的底层限制,不是 bug,所有基于 net/http 的框架都如此
- 如果你既要原始 JSON 字符串(比如做审计日志),又要结构化解析,必须二选一或提前缓存
- 不要在中间件里无意识调用
GetRawData(),否则下游 handler 会集体失效
想同时保留原始串和结构体解析,用 c.ShouldBindBodyWith()
这个方法会先读一次 body,缓存到 context 中,之后多次绑定都不再触发真实 IO。
- 只适用于需要多次解析同一 body 的场景(比如先校验签名,再解密,再绑定)
- 必须显式指定 binding 类型,例如
binding.JSON - 不能和
c.GetRawData()混用——一旦调用了后者,ShouldBindBodyWith就拿不到数据了
示例:
func handleLogin(c *gin.Context) {
var req struct {
Email string `json:"email"`
Pass string `json:"pass"`
}
// 第一次读 body 并缓存
if err := c.ShouldBindBodyWith(&req, binding.JSON); err != nil {
c.AbortWithStatusJSON(400, gin.H{"error": err.Error()})
return
}
// 此时还能再 bind 一次(复用缓存)
var backupReq struct{ Email, Pass string }
_ = c.ShouldBindBodyWith(&backupReq, binding.JSON)
// 也能手动取原始字节(从 context 缓存里)
raw, _ := c.Get("body") // 注意:c.ShouldBindBodyWith 会自动 set "body"
log.Printf("raw json: %s", string(raw.([]byte)))
}
调试时临时打印原始 JSON,别用 fmt.Println(c.GetRawData())
这样写看似简单,但极易引发线上问题:它不仅破坏后续绑定,还会让 panic 日志里出现空 body、字段缺失等“玄学错误”。
- 开发期调试推荐加个开关中间件,统一拦截并记录 raw body(用
ShouldBindBodyWith+c.Set()) - 生产环境禁止无条件调用
GetRawData(),尤其在全局日志中间件里 - 如果只是想看请求内容,用 curl -v 或 Postman 查看 Raw Request 更安全
真正麻烦的不是怎么取 JSON,而是取完之后 body 就没了——这个副作用容易被忽略,直到接口突然开始 400。


















