Golang如何在HTTP Server中处理文件上传

11次阅读

必须显式调用 ParseMultipartForm 才能解析 multipart 表单,否则 request.MultipartForm 和 request.FormFile 返回 nil 或报错;默认内存上限 32MB,超限报“request body too large”,建议提前设置如 r.ParseMultipartForm(10

Golang如何在HTTP Server中处理文件上传

使用 request.ParseMultipartForm 解析上传数据

gohttp.Request 默认不会自动解析 multipart 表单,必须显式调用 ParseMultipartForm 才能访问 request.MultipartFormrequest.FormFile。不调用会直接返回 nil 或报错 http: no such file

注意:该方法内部会读取并缓存整个请求体,若上传文件过大(默认上限 32MB),会触发 http: request body too large。建议提前设置上限:

err := r.ParseMultipartForm(10 << 20) // 10MB if err != nil {     http.Error(w, "invalid form", http.StatusBadRequest)     return }
  • ParseMultipartForm 参数是最大内存缓存字节数;超出部分会暂存到磁盘临时文件
  • 若设为 0,等价于 ParseForm,无法处理文件字段
  • 调用后才能安全使用 r.FormFile 或遍历 r.MultipartForm.File

request.FormFile 获取单个文件

适用于表单中只有一个文件字段(如 )的常见场景。它比手动从 MultipartForm.File 取值更简洁,且自动处理了边界情况。

典型错误是忽略返回的 header*multipart.FileHeader),而直接试图读取 file —— 实际上 file 是一个 io.ReadCloser,需后续读取内容:

立即学习go语言免费学习笔记(深入)”;

file, header, err := r.FormFile("avatar") if err != nil {     http.Error(w, "no file uploaded", http.StatusBadRequest)     return } defer file.Close()  // 保存到磁盘示例 dst, err := os.Create("/tmp/" + header.Filename) if err != nil {     http.Error(w, "save failed", http.StatusInternalServerError)     return } defer dst.Close() io.Copy(dst, file)
  • header.Size 是文件大小(字节),可用于限制上传体积
  • header.Header.Get("Content-Type") 可获取客户端声明的 MIME 类型,但不可信,需额外校验
  • 务必 defer file.Close(),否则可能泄漏文件描述符

处理多个同名文件或动态字段名

当表单含多个 name="files" 文件输入,或字段名由前端动态生成(如 files[0], files[1]),就不能只依赖 FormFile。此时应直接访问 r.MultipartForm.File map

if err := r.ParseMultipartForm(32 << 20); err != nil {     http.Error(w, "parse error", http.StatusBadRequest)     return }  for name, files := range r.MultipartForm.File {     if Strings.HasPrefix(name, "files[") {         for _, f := range files {             src, err := f.Open()             if err != nil {                 continue             }             defer src.Close() // 注意:这里不能 defer,需在循环内及时关闭              dst, _ := os.Create("/tmp/" + f.Filename)             io.Copy(dst, src)             dst.Close()         }     } }
  • r.MultipartForm.Filemap[string][]*multipart.FileHeader,每个 key 对应表单字段名
  • 循环内 defer src.Close() 会延迟到函数结束才执行,导致所有文件句柄积 —— 必须显式 src.Close()
  • 如果字段名不确定,可先用 r.MultipartForm.Value 检查普通文本字段辅助判断

避免临时文件残留和磁盘爆满

Go 在 ParseMultipartForm 中自动创建临时文件时,使用的是 os.TempDir()(通常是 /tmp)。若上传频繁或大文件未及时清理,可能占满磁盘。

有两条关键控制路径:

  • 通过 os.Setenv("TMPDIR", "/path/to/clean/tmp") 在程序启动时指定独立临时目录,并配 crontab 定期清理
  • 更稳妥的做法是绕过默认临时机制,用 multipart.NewReader 手动流式解析,边读边存、不缓存整份数据(适合超大文件或敏感内容)
  • 永远不要信任 Filename 字段 —— 它来自客户端,可能含 ../ 路径穿越,保存前必须 sanitize,例如用 filepath.Base(f.Filename)

最常被跳过的一步是文件名净化,哪怕只是本地测试,也建议统一加一层白名单校验,比如只允许 [a-zA-Z0-9._-]+ 模式。

text=ZqhQzanResources