Fiber中间件不能多次读取ctx.Body(),因其底层依赖Go HTTP的单次可读io.ReadCloser;首次调用即消费流,后续读取返回空或panic,需通过io.ReadAll缓存并ResetBody重置。

为什么 Fiber 的中间件不能直接读取 ctx.Body() 多次?
Fiber 默认的 ctx.Body() 底层调用的是 http.Request.Body,而 HTTP 请求体是单次可读流(io.ReadCloser)。一旦你调用一次 ctx.Body() 或 ctx.BodyParser(),底层的 Body 就被消费完毕;后续再调用会返回空字节或 panic(取决于是否已关闭)。
这不是 Fiber 的 bug,而是 Go HTTP 标准库的设计约束。常见错误现象包括:
- 中间件里解析了一次 JSON,Controller 里再解析就得到空结构体或
EOF错误 - 日志中间件打印了 body,但下游 handler 拿不到原始数据
- 使用
ctx.FormValue()后,ctx.Body()返回空
解决办法是提前缓存请求体——用 ctx.Request().Body 读一次,存到 ctx.Locals,再用 bytes.NewReader 重置给后续使用。
如何写一个只拦截 /api/v1/submit 的中间件并校验 JSON 字段?
不要在中间件里写完整业务逻辑,聚焦「拦截 + 初筛」。下面这个中间件只对匹配路径且 Content-Type 是 application/json 的请求做字段检查,其他一律放行:
func ValidateSubmitMiddleware() fiber.Handler {
return func(c *fiber.Ctx) error {
// 只拦截指定路径和类型
if c.Path() != "/api/v1/submit" || c.Get("Content-Type") != "application/json" {
return c.Next()
}
// 缓存并读取 body
body, err := io.ReadAll(c.Request().Body)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid request body"})
}
// 重置 Body,供后续 handler 使用
c.Request().ResetBody()
c.Request().SetBodyRaw(body)
// 解析并校验关键字段
var req struct {
UserID string `json:"user_id"`
Payload string `json:"payload"`
}
if err := json.Unmarshal(body, &req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid JSON format"})
}
if req.UserID == "" || len(req.Payload) > 10240 {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "user_id required or payload too large"})
}
// 写入 locals,供 controller 复用解析结果
c.Locals("parsed_submit", req)
return c.Next()
}
}
注册方式:
app.Post("/api/v1/submit", ValidateSubmitMiddleware(), submitHandler)
中间件里怎么安全地修改响应内容?
Fiber 不允许在中间件中直接调用 c.Send() 或 c.JSON() 后还执行 c.Next(),否则会 panic:「cannot set header after sent」。正确做法是用 c.Context().Response().SetBodyString() 配合状态码控制,且必须终止链路:
- 如果要拦截并返回自定义响应,必须显式 return,不调用
c.Next() - 想加 Header(比如防爬标记),可以用
c.Set("X-Processed-By", "validator"),它不会触发发送 - 若需重写整个响应体(如统一错误格式),建议用
c.Context().Response().SetStatusCode()+SetBodyString(),然后 return
错误示范:c.JSON(...); c.Next() → 必然报错;正确写法:return c.JSON(...)。
多个中间件的顺序和共享数据怎么处理?
Fiber 中间件是洋葱模型:请求从外向内,响应从内向外。顺序直接影响行为逻辑。比如鉴权中间件必须在参数校验之前,否则未登录用户也能触发耗时解析。
共享数据靠 c.Locals,它是请求生命周期内的 map[string]interface{},线程安全,但**不能跨请求复用**。常见误用:
- 把数据库连接、配置对象塞进
c.Locals(应该用依赖注入或全局变量) - 期望在
afterCompletion阶段读取c.Locals里的大对象(可能已被 GC 或内存泄漏) - 用
c.Locals["key"] = &struct{}后,在下一个中间件里修改字段,却忘了指针共享问题
真正该放进去的,只有当前请求上下文里轻量、临时、只读或明确生命周期的数据,比如解析后的 token、预校验过的 ID、临时生成的 traceID。


















