如何使用Golang实现文件上传服务器_Golang net/http 文件接收示例

必须先调用 ParseMultipartForm 设置内存限制,再调用 FormFile;否则会返回 http.ErrNotMultipart 错误,且未设 MaxMemory 可能导致大文件全载入内存。

如何用 net/http 正确接收 multipart 文件上传

Go 标准库的 net/http 支持原生 multipart 表单解析,但必须显式调用 ParseMultipartForm,否则 r.MultipartForm 为空,r.FormFile 会返回 http.ErrNotMultipart 错误。

常见错误是直接调用 r.FormFile("file") 而没提前解析,或忽略 MaxMemory 设置导致大文件被加载进内存。

  • ParseMultipartForm(32 表示最多 32MB 内存缓存,超出部分写入临时磁盘文件
  • 不调用该方法就访问 FormFile,会返回 http: multipart handled by ParseMultipartForm 类似错误
  • 若表单中含非文件字段(如 username),需在 ParseMultipartForm 后用 r.PostFormValue("username") 获取

http.HandleFunc 中处理上传并保存到本地文件

核心逻辑是:解析表单 → 获取文件句柄 → 创建目标文件 → 流式拷贝。避免一次性读入内存,尤其对视频、压缩包等大文件。

注意 multipart.Fileio.Reader,可直接传给 io.Copy;目标路径需确保父目录存在,否则 os.Create 会失败。

func uploadHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	err := r.ParseMultipartForm(32 << 20)
	if err != nil {
		http.Error(w, "Unable to parse form", http.StatusBadRequest)
		return
	}

	file, header, err := r.FormFile("file")
	if err != nil {
		http.Error(w, "No file received", http.StatusBadRequest)
		return
	}
	defer file.Close()

	dst, err := os.Create("./uploads/" + header.Filename)
	if err != nil {
		http.Error(w, "Cannot create file", http.StatusInternalServerError)
		return
	}
	defer dst.Close()

	if _, err := io.Copy(dst, file); err != nil {
		http.Error(w, "Failed to save file", http.StatusInternalServerError)
		return
	}

	w.WriteHeader(http.StatusOK)
	w.Write([]byte("Upload success"))
}

为什么 http.ServeFile 不适合直接提供上传后文件访问

http.ServeFile 默认不支持目录列表,且将 ./uploads/ 直接暴露为根路径易引发路径遍历风险(如请求 ../../etc/passwd)。更安全的做法是用 http.StripPrefix + http.FileServer 限定子路径,并禁用目录索引。

  • 错误写法:http.HandleFunc("/files/", http.ServeFile) —— 参数不匹配,ServeFile 不是 HandlerFunc
  • 正确做法:用 http.FileServer(http.Dir("./uploads")) 并包裹 StripPrefix
  • 必须手动创建 ./uploads 目录,否则首次访问返回 404

前端 HTML 表单必须满足的三个条件

后端能正常接收的前提是前端构造合法 multipart 表单。漏掉任一条件都会导致 FormFile 返回空或报错。

  • enctype="multipart/form-data" 必须显式声明,application/x-www-form-urlencoded 无法传二进制
  • name 值要和后端 r.FormFile("file") 中字符串完全一致
  • 不能用 fetchaxios 发送纯 JSON,必须用 FormData 构造(即使只传一个文件)

容易被忽略的是:开发时用 curl 测试,必须加 -F "file=@/path/to/file",而不是 -d