Beego不能直接用ctx.Input.RequestBody反复读取,因其为单次可读流;必须在BeforeRouter阶段手动缓存原始Body并替换为bytes.Reader,否则二次读取为空。

Beego 没有内置的“请求数据备份”中间件,也不能靠 ctx.Input.RequestBody 直接反复读取——因为 Go 的 http.Request.Body 是单次可读流,读完就 EOF。真要实现请求体(尤其是 JSON、form)的多次访问或落盘备份,必须在早期阶段手动缓存。
为什么不能直接用 ctx.Input.RequestBody 备份
Beego 的 ctx.Input.RequestBody 本质是 io.ReadCloser 的一次性拷贝,只在首次调用 ctx.Input.CopyBody() 或启用 CopyRequestBody = true 时触发;之后再读就是空。若你在中间件里先读一次、控制器里又读一次,第二次必然失败。
-
CopyRequestBody = true是全局开关,开启后所有请求都内存缓存整个 body,有内存放大风险(尤其大文件上传) - 即使开了,
RequestBody仍是原始字节切片,不解析、不校验,无法直接用于结构体绑定 - 静态资源(
/static/)走BeforeStatic阶段,此时RequestBody还未被框架处理,CopyBody()也无效
正确做法:在 BeforeRouter 中拦截并缓存原始 body
必须在路由匹配前(beego.BeforeRouter)把原始 Request.Body 读出、存到 ctx.Input.Data 或自定义字段,并替换为可重放的 bytes.Reader。这样后续 Controller 和 ORM 绑定才能正常工作。
- 注册位置必须早于
beego.Run(),且用beego.InsertFilter("/*", beego.BeforeRouter, backupBodyFilter) - 函数内需完整读取
ctx.Request.Body,用io.ReadAll+bytes.NewReader替换它 - 缓存内容建议存进
ctx.Input.Data["raw_body"] = bodyBytes,方便后续日志或审计使用 - 若只针对 POST/PUT,开头加判断:
if ctx.Request.Method != "POST" && ctx.Request.Method != "PUT" { ctx.Next(); return }
func backupBodyFilter(ctx *context.Context) {
if ctx.Request.Method != "POST" && ctx.Request.Method != "PUT" {
ctx.Next()
return
}
body, err := io.ReadAll(ctx.Request.Body)
if err != nil {
ctx.Abort(400, "failed to read request body")
return
}
ctx.Input.Data["raw_body"] = body
ctx.Request.Body = io.NopCloser(bytes.NewReader(body))
ctx.Next()
}
备份后如何安全使用(避免重复解析或 panic)
备份只是第一步,真正使用时容易踩两个坑:一是 Controller 里仍调 ParseForm() 或 json.Unmarshal 导致冲突;二是没区分 content-type 就硬解 JSON,遇到 multipart 会 panic。
- 对
application/json:用json.Unmarshal(ctx.Input.Data["raw_body"].([]byte), &v),别再碰RequestBody - 对
application/x-www-form-urlencoded:用url.ParseQuery(string(body))手动解析,ParseForm()会重新读 Body 并失败 - 对
multipart/form-data:不要尝试备份——文件上传本就不该进内存,应改用临时磁盘中转 + 文件哈希记录,或跳过备份 - 日志写入建议限制长度:
log.Printf("backup: %s", string(body[:min(len(body), 1024)]))
生产环境必须考虑的副作用
请求体备份不是零成本操作。高频小请求影响不大,但一旦涉及文件上传、批量导入等场景,内存和 GC 压力会明显上升。
- 务必加 size 限制:在
backupBodyFilter开头检查ctx.Request.ContentLength,超 2MB 直接跳过或返回 413 - 禁用对静态路径的备份:
beego.InsertFilter("/static/", beego.BeforeStatic, ...)不要注册 body 备份逻辑,BeforeStatic阶段无法安全读 Body - 若用 Redis 做异步备份,别在过滤器里阻塞写入——改用 goroutine + channel 异步推送,避免拖慢主请求链路
最麻烦的其实是边界:比如同一个请求既走 API 又走文件上传,或者前端发错 content-type。备份逻辑越靠前,越要精简、越要防御性判断——它不是功能增强,而是为可观测性付出的确定性代价。


















