必须在 handler 开头用 http.MaxBytesReader 包装 c.Request().Body,否则 c.FormFile 或 c.ParseMultipartForm 会直接读取未限制的请求体,导致内存耗尽;顺序错误或中间件注册时机不当均会使流控失效。

为什么 c.FormFile 前必须用 http.MaxBytesReader 包装 Body
Fiber 没有全局上传大小限制机制,c.FormFile 或 c.ParseMultipartForm 一旦被调用,Go 标准库的 multipart.Reader 就会开始读取整个请求体——此时再设限已无效。攻击者可发送 10GB 的伪造文件,服务端在解析 boundary 时就已开始接收并缓冲数据,内存或磁盘可能被耗尽。
唯一可靠的前置拦截点是:在 handler 开头、任何表单操作之前,用 http.MaxBytesReader 替换原始 c.Request().Body。这不是可选优化,而是安全底线。
-
http.MaxBytesReader(c.Context(), r.Body, 12*1024*1024)—— 总请求体上限(含所有文本字段 + 所有文件字节)建议设为 12–15MB,而非“仅文件 5MB” - 不能写成
c.FormFile("file"); r := c.Request(); r.Body = http.MaxBytesReader(...)—— 顺序错,完全失效 -
c.FormFile返回的file.Size是客户端声明值,可任意伪造;它不能替代流控,仅适合业务层二次校验
多文件上传时怎么避免 ParseMultipartForm 调用失败
c.MultipartForm() 不是懒加载,它依赖 c.ParseMultipartForm 是否已执行。若没显式调用,直接访问 c.MultipartForm().File["files"] 会返回 nil,导致 panic 或静默失败。
正确流程是固定两步:先 c.ParseMultipartForm(32 (内存阈值设为 32MB),再取文件切片。注意这个阈值只控制“多少字节存内存”,不影响 <code>MaxBytesReader 的总上限。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- 必须在
http.MaxBytesReader之后调用c.ParseMultipartForm,否则解析过程仍可能超限 - 多文件场景下,
c.MultipartForm().File["files"]返回的是[]*multipart.FileHeader,需遍历逐个处理 - 调用后务必加
defer c.MultipartForm().RemoveAll(),否则临时文件不清理,磁盘会被撑爆
c.SaveFile 为什么不受 MaxBytesReader 控制
c.SaveFile 底层调用的是 file.Open() + io.Copy,它读取的是 multipart 解析后已缓存在内存或临时磁盘中的文件句柄——而 http.MaxBytesReader 只作用于原始请求体流。也就是说,只要请求体本身没超限,后续 SaveFile 就能完整写出,哪怕目标磁盘空间不足。
- 这意味着你仍需对
file.Size做业务校验(比如拒绝 >2MB 的单个文件),但该值不可信,必须配合前置流控 - 若需更细粒度控制(如按文件名后缀限制大小),应在遍历
c.MultipartForm().File后,对每个FileHeader.Size做判断并提前 return - 不要依赖
c.Context().SetBodyLimit()—— Fiber v2 中该方法不生效,v3 已废弃,文档未明确但实测无效
中间件里放 MaxBytesReader 为什么常常失效
Fiber 的中间件执行时机在路由匹配**之后**。例如你注册了 app.Post("/upload", uploadHandler),又加了 app.Use("/upload", limitMiddleware),那么 limitMiddleware 实际在 uploadHandler 内部逻辑执行完才运行——此时 c.FormFile 早已触发,流控彻底迟到。
想用中间件统一处理,唯一可靠方式是把它注册在最顶层:app.Use(limitMiddleware),且必须在所有 app.Get/app.Post 之前。但这样会拦截全部请求,包括静态资源、健康检查等,得额外白名单过滤。
- 更推荐做法:每个上传 handler 开头固定三行:
r := c.Request(); r.Body = http.MaxBytesReader(c.Context(), r.Body, 12 - 别用
r.ResetBody()或r.SetBodyStream()手动重置——复杂、易出错,且和MaxBytesReader配合时可能破坏 Reader 链 - 如果用了
fiber.New(&fiber.Config{Immutable: true}),记得http.MaxBytesReader的第二个参数要传r.Body原始引用,不是拷贝后的
MaxBytesReader 当成“文件大小限制”来理解,而它真正限制的是整个 HTTP 请求体字节数。文本字段、多个文件、boundary 分隔符全算在内。业务上允许 5 个 2MB 文件,就得预留至少 12MB 上限——少 1 字节,合法请求也会被截断返回 413。

















