ctx.Request().Body只能读一次,因其底层是单次消费的io.ReadCloser;解决方法是在中间件中提前读取并缓存为bytes.Reader重新赋值,同时注意Content-Length更新、大小限制及multipart场景规避。

为什么 ctx.Request().Body 只能读一次?
因为 Go 标准库的 http.Request.Body 是一个 io.ReadCloser,底层通常是单次消费的流(比如 bytes.Reader 或网络连接缓冲区)。Iris 基于 net/http,所以同样受限——一旦你调用 ctx.PostValue、ctx.ReadBody 或手动 io.ReadAll(ctx.Request().Body),原始 Body 就被读空了,后续再读会得到空字节或 EOF。
怎么在中间件里安全地重复读取请求体?
核心思路是:在中间件早期把原始 Body 读出来、缓存为字节切片,然后用 bytes.NewReader 重新包装成可重读的 io.ReadCloser,再替换掉 ctx.Request().Body。注意必须在任何其他处理器读取 Body 前执行,否则缓存就晚了。
- 用
ctx.Request().Body读取全部内容到[]byte,并检查错误(如超大请求需限制) - 调用
ctx.Request().Body.Close(),避免资源泄漏 - 用
bytes.NewReader(cachedBytes)创建新 reader,并包装成io.NopCloser - 通过
ctx.Request().Body = newBody替换(Iris 允许直接赋值) - 把缓存的
[]byte存到ctx.Values().Set("rawBody", cachedBytes),供后续处理器取用
示例中间件:
func ReusableBodyMiddleware() iris.Handler {
return func(ctx iris.Context) {
body, err := io.ReadAll(ctx.Request().Body)
if err != nil {
ctx.StatusCode(400)
ctx.WriteString("failed to read body: " + err.Error())
return
}
ctx.Request().Body.Close()
newBody := io.NopCloser(bytes.NewReader(body))
ctx.Request().Body = newBody
ctx.Values().Set("rawBody", body)
ctx.Next()
}
}
使用时要注意哪些坑?
这个方案看着简单,但几个关键点不处理就会出问题:
-
Content-Length头不会自动更新,如果下游逻辑依赖它(比如某些代理或限流中间件),需要手动重设:ctx.Request().Header.Set("Content-Length", strconv.Itoa(len(body))) - 大请求(如文件上传)会吃内存,务必加长度限制,比如在
io.ReadAll前用io.LimitReader(ctx.Request().Body, maxLen) - Iris 的
ctx.ReadJSON等方法内部也会读 Body,所以中间件必须注册在所有依赖 Body 的处理器之前(即app.Use(ReusableBodyMiddleware())要放在最前面) - 如果请求是
multipart/form-data,不要用这个中间件——它会破坏 boundary 解析,此时应改用ctx.FormFile或提前解析表单
有没有更轻量的替代方案?
如果你只需要在某个特定 handler 里多次读,而不是全局复用,其实没必要写中间件。直接在 handler 内部缓存更可控:
func myHandler(ctx iris.Context) {
body, _ := io.ReadAll(ctx.Request().Body)
ctx.Request().Body.Close()
ctx.Request().Body = io.NopCloser(bytes.NewReader(body))
// 第一次用
var data map[string]interface{}
json.Unmarshal(body, &data)
// 第二次用(比如日志、鉴权)
log.Printf("raw body: %s", body)
}
全局中间件适合审计、签名验证等跨业务场景;局部处理更适合逻辑收敛、避免副作用。真正难的不是复制 Body,而是判断「谁该负责缓存」和「缓存多久」——这两点没想清楚,加了中间件反而让请求链路更难 debug。


















