MaxMultipartMemory 是不是“最大上传大小”?不是。这篇用真实的 multipart 上传测它的真正含义:超过它的文件去了哪里、内存占用差多少、请求结束后临时文件还在不在,以及怎样用 MaxBytesReader 真正限制总大小。
gin.New().MaxMultipartMemory = 33554432(= 32 MiB)
context.go 的 FormFile(Gin v1.12.0):
698 | func (c *Context) FormFile(name string) (*multipart.FileHeader, error) {
699 | if c.Request.MultipartForm == nil {
700 | if err := c.Request.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil {
701 | return nil, err
702 | }
703 | }
704 | f, fh, err := c.Request.FormFile(name)
705 | if err != nil {
706 | return nil, err
707 | }
708 | f.Close()
709 | return fh, err
710 | }MaxMultipartMemory 就是 ParseMultipartForm 的 maxMemory 参数:multipart 表单里最多多少字节放在内存里,其余放磁盘临时文件。FormFile 第一次调用时才解析整个表单,之后复用解析结果。
MaxMultipartMemory=1 MiB → 200 FormFile 之后临时目录:multipart(5.0 MiB);fh.Size=5.0 MiB;请求结束后临时目录:无
MaxMultipartMemory=64 MiB → 200 FormFile 之后临时目录:无;fh.Size=5.0 MiB;请求结束后临时目录:无文件小于上限就全在内存里,超过就落到临时目录($TMPDIR/multipart-*),fh.Size 与读取方式不变。临时文件在请求结束时由 net/http 删除;所以只要请求是正常结束的,临时目录不会越积越多。
第 1 轮 MaxMultipartMemory=1 MiB:分配 4.1 MiB,耗时 90 ms,err=<nil>
第 1 轮 MaxMultipartMemory=32 MiB:分配 128.1 MiB,耗时 73 ms,err=<nil>
第 1 轮 MaxMultipartMemory=64 MiB:分配 128.0 MiB,耗时 38 ms,err=<nil>
第 2 轮 MaxMultipartMemory=1 MiB:分配 4.0 MiB,耗时 95 ms,err=<nil>
第 2 轮 MaxMultipartMemory=32 MiB:分配 128.1 MiB,耗时 66 ms,err=<nil>
第 2 轮 MaxMultipartMemory=64 MiB:分配 128.0 MiB,耗时 35 ms,err=<nil>放内存的文件,解析期间的累计分配约是文件大小的 3 倍(缓冲区增长时反复复制,TotalAlloc 不是峰值,但说明 GC 压力);限制设成 32 MiB、文件 40 MiB 时,分配量和全放内存一样大(我推测是先在内存里读到上限再落盘,没有逐行验证)。想让大文件尽快落盘,把上限设小(如 1 MiB),代价是每次都有磁盘 I/O。并发上传时每个请求都占一份。
/open 上传 200 MiB → 200 收下 200 MiB,临时目录:multipart(200.0 MiB)
/capped(上限 8 MiB)上传 5 MiB → 200 收下 5 MiB
/capped(上限 8 MiB)上传 20 MiB → 413 超过上限 8388608 字节:http: request body too largeMaxMultipartMemory 不限制总大小:200 MiB 的文件会被完整写到临时目录。要限制总大小用 http.MaxBytesReader 包住 c.Request.Body,超限时 FormFile 返回 http: request body too large,可以用 errors.As 取 *http.MaxBytesError 并返回 413。
MaxMultipartMemory=1 MiB,文本字段 2 MiB → 200 字段数 1
MaxMultipartMemory=1 MiB,文本字段 10 MiB → 200 字段数 1
MaxMultipartMemory=1 MiB,文本字段 12 MiB → 400 multipart: message too large非文件字段也受限制:单个字段超过 maxMemory + 10 MiB 就报 multipart: message too large。1 MiB 的上限下,10 MiB 的文本字段能过,12 MiB 的不行。