必须显式设置 maxmemory 限制内存缓冲区,超出部分自动落盘,但总大小需手动检查,否则可能导致 oom 或磁盘写满;建议设为 32

用 http.Request.ParseMultipartForm 控制上传大小上限
Go 默认不限制 multipart 表单解析的内存和磁盘使用量,不设限直接调用 r.ParseMultipartForm 可能导致 OOM 或磁盘写满。必须在调用前显式设置最大内存阈值(maxMemory)——它不是“总大小限制”,而是内存缓冲区上限,超出部分会自动落盘,但总大小仍需手动检查。
实操建议:
- 调用
r.ParseMultipartForm(32 (即 32MB 内存缓冲),避免小文件全进内存,也防止大文件无节制占用内存 - 之后立即读取
r.MultipartForm.File获取文件头信息,再结合file.Size做总大小校验,不能只信maxMemory - 若业务要求硬性限制 10MB 总大小,需在获取每个
*multipart.FileHeader后判断fh.Size > 10<code>10241024,超限直接返回 400
用 filepath.Ext 和 MIME 类型双重校验文件类型
仅靠文件扩展名(如 .jpg)极易伪造;仅靠 fh.Header.Get("Content-Type") 又可被客户端随意篡改。必须读取文件前若干字节,用 net/http.DetectContentType 或更可靠的 github.com/h2non/filetype 做二进制检测。
实操建议:
- 打开文件句柄后,用
io.LimitReader(f, 512)读前 512 字节,传给filetype.Match判断真实类型 - 白名单应同时包含扩展名和 MIME 类型:例如允许
image/jpeg且扩展名为.jpg或.jpeg,二者都匹配才放行 - 注意
fh.Header.Get("Content-Type")在某些浏览器或工具中可能为空或为application/octet-stream,不能作为唯一依据
上传临时文件后及时清理,避免 os.Remove 被忽略
Go 的 r.FormFile 或 fh.Open() 返回的文件句柄底层可能是临时磁盘文件(当超过 maxMemory 时)。如果业务逻辑 panic、提前 return 或忘记 defer f.Close(),这些临时文件不会自动删除,久而久之填满 /tmp。
实操建议:
- 用
fh.Open()获取文件后,立刻用f.Stat()检查是否是临时路径(如含/tmp/或os.TempDir()),若是则务必在处理完后os.Remove(f.Name()) - 不要依赖
defer f.Close()清理临时文件——f.Close()只关闭句柄,不删文件 - 更稳妥的做法:用
io.Copy把内容转存到业务目标路径后,再显式os.Remove(fh.Filename)(注意fh.Filename是原始客户端名,真正临时路径在f.Name())
并发上传时注意 maxMemory 是 per-request 的,但磁盘 I/O 会叠加
ParseMultipartForm 的 maxMemory 参数对每个请求独立生效,看似安全。但高并发下大量请求触发落盘,多个临时文件同时写入同一磁盘分区(尤其是默认 /tmp),I/O 瓶颈和空间耗尽风险陡增。
实操建议:
- 把临时目录指向独立 SSD 分区,通过
os.Setenv("TMPDIR", "/mnt/upload-tmp")提前设置(需在http.ListenAndServe前) - 用
golang.org/x/sync/semaphore限制并发解析 multipart 的请求数,比如最多 20 个同时解析,避免瞬时磁盘打满 - 监控
/mnt/upload-tmp使用率,接近 80% 时主动返回 503,而不是等open /tmp/xxx: no space left on device这种错误发生











