不能在中间件中直接调用 c.body(),因其返回的 io.readcloser 只能读一次,重复调用会导致后续 handler 读空或因 bom/非 utf-8 字节 panic;正确做法是用 c.request().body() + io.readall + bytes.newreader + setbodystream 缓存原始 body。

中间件里直接调 c.Body() 会读空或 panic
Fiber 的 *fiber.Ctx 是复用对象,c.Body() 返回的是一个 io.ReadCloser,只能读一次。中间件中若调了 c.Body(),后续路由 handler 再调就会读到空字节;更糟的是,如果请求体含 BOM 或非 UTF-8 字节(比如微信回调的 XML),c.Body() 会尝试自动解码并直接 panic。
- 不要在中间件里反复调
c.Body()—— 它不是“取值函数”,而是“消费流” - 不要依赖
c.FormValue()或c.Struct():这些方法内部会触发 body 解析,且只对特定Content-Type生效,对 text/xml 或原始二进制无效 - 若需日志记录原始请求体,必须提前读取、缓存,并确保不破坏后续逻辑
正确做法:用 c.Request().Body() + bytes.NewReader 缓存
绕过 Fiber 的自动解析层,直取底层 fasthttp 的原始 body 流,手动读取为字节切片,再封装回 io.Reader 供后续使用。
- 先调
c.Request().Body()获取原始io.ReadCloser - 用
ioutil.ReadAll(Go io.ReadAll(Go ≥ 1.16)读出全部字节 - 把字节切片转成
bytes.NewReader(bodyBytes),再通过c.Request().SetBodyStream()写回,恢复可读状态 - 打印时注意长度限制,避免日志爆炸(比如只截前 512 字节)
示例代码:
func LogRequestBody() fiber.Handler {
return func(c *fiber.Ctx) error {
// 1. 获取原始 body 流
bodyStream := c.Request().Body()
if bodyStream == nil {
return c.Next()
}
// 2. 读全部字节(注意:大文件会吃内存,生产慎用)
bodyBytes, err := io.ReadAll(bodyStream)
if err != nil {
return c.Next()
}
// 3. 打印(建议加长度限制)
if len(bodyBytes) > 0 {
log.Printf("REQ BODY [%s %s]: %s", c.Method(), c.Path(), string(bodyBytes[:min(len(bodyBytes), 512)]))
}
// 4. 把 body 写回去,供后续 handler 正常读取
c.Request().SetBodyStream(bytes.NewReader(bodyBytes), int64(len(bodyBytes)))
return c.Next()
}
}
为什么不能全局启用这个中间件
因为 io.ReadAll 会把整个请求体加载进内存,对文件上传、大 JSON、流式请求等场景极易 OOM。它只适合调试或低流量管理接口。
- 仅对明确需要日志的路径启用,例如
app.Use("/api/debug", LogRequestBody) - 避免和
BodyLimit、Compress等中间件冲突——它们也操作 body 流,顺序错乱会导致读不到或 double-read - 微信/支付宝回调等关键路径严禁使用该中间件:它们本就不该被任何中间件提前读 body,应单独注册路由并跳过所有全局中间件
替代方案:只记录关键字段,不碰 body
多数日志需求其实不需要完整 body,只需 method、path、query、headers、size 和 status。这些信息无需读 body 就能拿到,安全又轻量。
- 用
c.Method()、c.Path()、c.Get("User-Agent")、c.Query("q")等组合构造结构化日志 - 用
c.Request().Header.ContentLength()获取 body 长度(即使没 Content-Length 头,fasthttp 也会尽力推断) - 真正要审计 body 时,应在业务 handler 内按需解析(如
c.Struct(&v)),而非在中间件里盲目抓取
最常被忽略的一点:你加的日志中间件,可能正在悄悄杀死支付回调的验签流程。











