必须先调用 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.File 是 io.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后端能正常接收的前提是前端构造合法 multipart 表单。漏掉任一条件都会导致 FormFile 返回空或报错。
enctype="multipart/form-data" 必须显式声明,application/x-www-form-urlencoded 无法传二进制 的 name 值要和后端 r.FormFile("file") 中字符串完全一致fetch 或 axios 发送纯 JSON,必须用 FormData 构造(即使只传一个文件)容易被忽略的是:开发时用 curl 测试,必须加 -F "file=@/path/to/file",而不是 -d。