必须先调用 ParseMultipartForm,否则直接访问 r.MultipartForm 或 r.FormFile 会返回 nil 或报错“http: multipart: NextPart: no multipart boundary”;正确做法是先调用 r.ParseMultipartForm(32

Go 的 http.Request.ParseMultipartForm 必须先调用
不调用 ParseMultipartForm 就直接读 r.MultipartForm 或遍历 r.FormFile,会返回 nil 或报错 http: multipart: NextPart: no multipart boundary。这是因为 Go 默认不自动解析 multipart body,必须显式触发解析。
常见错误写法:
file, _, err := r.FormFile("file") // 未 ParseMultipartForm 前,这里 err != nil
正确做法是:在读取文件前,先调用 ParseMultipartForm 并传入最大内存阈值(单位字节):
r.ParseMultipartForm(32 表示最多 32MB 在内存中解析,超限部分写入临时磁盘文件- 阈值设太小(如
1 )会导致稍大文件频繁落盘,影响性能;设太大可能 OOM - 如果只处理小文件且内存充足,可设为
0(等价于math.MaxInt64),但生产环境不推荐
r.FormFile 和 r.MultipartForm.File 的区别与选用场景
r.FormFile("name") 是快捷方法,内部会自动调用 ParseMultipartForm(若未调用过),并返回第一个同名文件的 multipart.File 和 multipart.FileHeader;而 r.MultipartForm.File["name"] 是解析后直接访问字段,适合多文件或需批量处理。
立即学习“go语言免费学习笔记(深入)”;
- 单文件上传、简单表单 → 用
r.FormFile更简洁 - 支持多文件(
)、或需校验多个字段 → 先ParseMultipartForm,再查r.MultipartForm.File -
r.MultipartForm.File返回的是[]*multipart.FileHeader,每个元素需用Open()才能获取可读流
保存文件时务必检查 FileHeader.Size 和 FileHeader.Filename
攻击者可伪造超大 Content-Length 或空/路径遍历文件名(如 ../../etc/passwd),不校验会引发磁盘爆满或任意文件写入。
- 用
filepath.Base(h.Filename)提取原始文件名,丢弃路径部分 - 限制大小:例如
if h.Size > 10 - 建议生成服务端唯一文件名(如 UUID),避免依赖客户端传入的
Filename - 保存前确保目标目录存在:
os.MkdirAll(dir, 0755),否则os.Create会失败
上传大文件时注意超时和流式处理
默认 HTTP server 超时(ReadTimeout)通常为 30 秒,上传几百 MB 文件极易超时;同时,若把整个文件读进内存(如 io.ReadAll(file)),会吃光 RAM。
- 设置更长的
http.Server.ReadTimeout和WriteTimeout(如 5 分钟) - 用
io.Copy直接流式写入磁盘:io.Copy(dst, file),避免中间缓冲 - 如需进度追踪或断点续传,得自己实现分块读取 +
file.Seek,标准库不提供 - 生产环境建议搭配 Nginx 做前置上传代理(用
client_max_body_size和proxy_buffering off控制)
边界情况比想象中多:文件名编码异常、空文件、Content-Type 缺失、boundary 不合法……真正健壮的上传逻辑,90% 都在防御性检查里。










