MaxMultipartMemory 仅限制内存缓冲区大小,不拦截超大文件上传;真正有效的总请求体限制需结合 http.MaxBytesReader 中间件(413 响应)与 handler 内 file.Size 校验,三者缺一不可。

为什么 MaxMultipartMemory 设置后仍能上传超大文件?
因为 MaxMultipartMemory 只控制内存中解析 multipart 表单的缓冲区大小,不拦截磁盘上传;真正限制文件总大小(含所有字段+文件)的是 MaxMultipartMemory + FormFile 读取逻辑配合 http.MaxBytesReader 才有效。常见错误是只设前者,结果 c.FormFile("file") 调用时仍卡住或 OOM。
- 若上传总请求体(含表单字段+文件头)超过
MaxMultipartMemory,Gin 会自动将文件写入临时磁盘,但不会拒绝请求 - 必须在路由处理函数内手动检查文件大小,或在中间件中用
http.MaxBytesReader包裹c.Request.Body -
MaxMultipartMemory单位是字节,设为32 表示 32MB 内存缓冲上限
在 Gin 启动时设置全局 MaxMultipartMemory
这是最基础的防线,影响所有路由对 multipart 表单的初始解析行为。它不能替代运行时校验,但能减少小文件误传的资源消耗。
r := gin.Default()
r.MaxMultipartMemory = 32 << 20 // 32MB
r.POST("/upload", func(c *gin.Context) {
file, err := c.FormFile("file")
if err != nil {
c.String(400, "parse error: %v", err)
return
}
// 注意:此时 file.Size 是声明大小,不一定真实(可能被篡改)
if file.Size > 32<<20 {
c.String(400, "file too large: %d bytes", file.Size)
return
}
// ...
})
用 http.MaxBytesReader 在中间件中硬限请求体总长
这是最可靠的方式——在请求体被读取前就设定硬上限,超出即 413。适用于所有上传接口,且不受 multipart 解析逻辑干扰。
- 必须在
c.Request.Body被任何 handler 读取前替换,否则无效 - 限值应略大于预期最大文件 + 表单开销(例如 33MB 对应 32MB 文件)
- 错误响应状态码应为
413 Payload Too Large,符合 HTTP 语义
func limitBodySize(max int64) gin.HandlerFunc {
return func(c *gin.Context) {
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, max)
c.Next()
if c.Writer.Status() == http.StatusRequestEntityTooLarge {
c.String(http.StatusRequestEntityTooLarge, "file too large")
}
}
}
r := gin.New()
r.Use(limitBodySize(33 << 20)) // 33MB 总请求体上限
r.POST("/upload", uploadHandler)
为什么不能只依赖 file.Size 做判断?
file.Size 来自 multipart header 中的 Content-Length 字段,客户端可随意伪造。攻击者发一个声明 1KB 实际 1GB 的文件,FormFile 仍会尝试打开并读取——直到磁盘写满或超时。
- 必须结合
http.MaxBytesReader或流式读取 + 计数器实时校验 - 若需精确控制单个文件大小且允许多文件,应在
c.MultipartForm()后遍历form.File并逐个Open()+io.CopyN校验 - 生产环境建议同时启用两种机制:中间件限总长 + handler 内二次校验声明大小与实际读取量
MaxMultipartMemory 是开关,http.MaxBytesReader 是保险丝,而 file.Size 只是个参考数字——三者缺一不可,尤其别忽略中间件里那行 c.Request.Body = ... 替换。


















