必须先调用 r.ParseMultipartForm(32 << 20) 或 r.ParseForm,再调用 r.FormFile("file"),否则 r.MultipartForm 为空且 r.FormFile 返回 http.ErrMissingFile。

如何用 http.HandleFunc 正确接收 multipart 文件
Go 标准库的 multipart/form-data 解析不自动读取请求体,必须显式调用 r.ParseMultipartForm 或 r.ParseForm,否则 r.MultipartForm 为空,r.FormFile 会返回 http.ErrMissingFile。
常见错误是直接调用 r.FormFile("file") 却没先解析——尤其在调试时看到空文件或 panic,大概率卡在这步。
r.ParseMultipartForm(32 是必须的,参数表示内存缓冲上限(字节),超出会写入临时磁盘;设太小(如 <code>1)上传大文件时可能静默失败- 如果只传文件不传其他字段,
r.ParseForm()不够用,它只处理urlencoded,对multipart无效 - 调用
r.FormFile前确保Content-Type真的是multipart/form-data,否则ParseMultipartForm会返回err: invalid MIME type
FormFile 和 MultipartForm.File 该怎么选
r.FormFile("name") 是快捷方式,内部调用 r.MultipartForm 并返回第一个匹配文件;而 r.MultipartForm.File["name"] 返回切片,适合多文件同名场景(比如前端用 input[type=file] multiple)。
但注意:r.MultipartForm 只有在成功调用 ParseMultipartForm 后才可用;且若表单里有同名文本字段(如 <input name="file">),r.MultipartForm.File 里不会包含它,它只存文件项。
立即学习“go语言免费学习笔记(深入)”;
- 单文件上传:优先用
r.FormFile,代码简洁,错误明确 - 多文件上传:必须用
r.MultipartForm.File["files"]遍历切片,再对每个*multipart.FileHeader调用Open() - 混传文件+文本字段时,文本值走
r.PostFormValue("field"),别误用MultipartForm.Value—— 它只在ParseMultipartForm后存在,且不含文件字段
保存文件时为什么总遇到 no such file or directory
错误通常不是路径写错,而是目标目录根本不存在。Go 的 os.Create 或 f, _ := header.Open() 后直接 io.Copy,不会自动创建父目录。
另一个坑是文件名没清理:header.Filename 来自客户端,可能含 ../、空格、Unicode 控制符,直接拼路径可能越权或写失败。
- 务必用
os.MkdirAll(filepath.Dir(dstPath), 0755)创建上级目录,别假设目录已存在 - 用
filepath.Base(header.Filename)截掉路径部分,再白名单过滤字符(比如只留字母、数字、下划线、点) - 避免用
time.Now().Unix()做文件名后缀——并发高时可能重复,建议用uuid.New().String()或加随机后缀
上传大文件时连接被重置或超时
默认 HTTP server 没配超时,但实际常被中间件(Nginx、Cloudflare)、客户端(fetch timeout)、甚至 Go 的 http.Server.ReadTimeout 截断。上传 100MB 文件若只设 30 秒读超时,基本必断。
更隐蔽的问题是内存占用:即使设了 ParseMultipartForm(32,Go 仍会把整个文件内容读进内存再写磁盘(当文件 ≤ 内存限额时),导致 GC 压力和 OOM。
- 生产环境必须设置
http.Server.ReadTimeout和ReadHeaderTimeout,例如30 * time.Second和5 * time.Second - 大文件上传建议关掉内存缓冲:用
r.ParseMultipartForm(0)强制全部落盘,再用header.Open()流式读取 - 不要用
ioutil.ReadAll(f)读整个文件——它会吃光内存;改用io.CopyN(dst, src, limit)或分块io.Copy
边界情况永远比想的多:客户端取消上传、网络抖动、杀进程、磁盘满……文件操作前最好先 os.Stat 检查目标路径可写,保存后校验 os.Stat 大小是否匹配 header.Size,不然你以为传完了,其实只写了一半。










