必须先调 r.parsemultipartform,否则所有字段为空;r.parseform() 对 multipart 无效;参数是内存缓冲上限而非总请求大小限制。

Go 原生 net/http 就能完整处理文件上传,不需要框架——所谓“框架封装”只是帮你自动调了 r.ParseMultipartForm 或包了一层错误处理,底层逻辑完全一致。跳过解析步骤、误信客户端字段、忽略临时文件清理,这三件事做错任意一个,上传接口就会在生产环境静默失败。
必须先调 r.ParseMultipartForm,否则所有字段都为空
Go 对 multipart/form-data 是懒解析:不主动读 r.Body,r.FormValue 返回空字符串,r.FormFile 返回 (nil, http: no such file),连错误都不抛。这不是前端没传,是 Go 故意卡着不给你数据。
-
r.ParseForm()只处理application/x-www-form-urlencoded,对 multipart 完全无效 - 参数不是“总请求大小限制”,而是“内存缓冲上限”——比如设
10 (10MB),非文件字段和小文件放内存,超的部分自动写入 <code>os.TempDir()下的临时文件 - 设成
0会 fallback 到默认 32MB,语义模糊;设太大(如math.MaxInt64)可能被巨量小字段打爆内存
多文件上传必须用 r.MultipartForm.File,别只靠 r.FormFile
r.FormFile("files") 内部会触发一次 ParseMultipartForm,但它只返回同名字段里的第一个 *multipart.FileHeader。如果 HTML 是 <input type="file" name="files" multiple>,用户选了 5 个文件,r.FormFile 只给你第一个,其余 4 个静默丢弃。
- 正确做法是先确保已调过
r.ParseMultipartForm,再访问r.MultipartForm.File["files"],它是个切片,长度就是实际上传个数 - 遍历时要检查切片是否为空:
if len(files) == 0,否则后续range直接跳过,逻辑中断却无提示 -
FileHeader里只有元信息可信(Filename、Size、Header),内容要靠hdr.Open()拿io.ReadCloser流,且必须defer f.Close()
保存文件时不能直接 ioutil.WriteFile,流式写入+校验缺一不可
把整个文件加载进内存再写盘,大文件直接 OOM;拼接路径用字符串加法,目录遍历漏洞立刻上线;不校验 MIME 类型只看扩展名,恶意用户改个后缀就能上传 WebShell。
- 路径必须用
filepath.Join(uploadDir, safeFilename),safeFilename要去掉../、清空路径分隔符、强制重命名(如加 UUID 前缀) - 写入用
os.OpenFile(..., os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644),显式权限,避免 umask 干扰 - 复制用
io.CopyN(dst, src, maxFileSize)限大小,防攻击者传 10GB 垃圾文件占满磁盘 - 关键文件写完立即
dst.Sync()确保落盘,再dst.Close();忘记Close()并发高时会触发 “too many open files”
真正容易崩的不是解析,是信任边界没划清
客户端控制的一切都不可信:Content-Type 是伪造的,Size 字段可篡改,boundary 能构造异常值让 Go 解析器 panic,Filename 里藏 ../../../etc/passwd 也完全合法。
-
Header.Get("Content-Type")只能当参考,真实类型要用github.com/h2non/filetype读 magic bytes 校验 - 临时文件由
r.ParseMultipartForm自动创建,但清理必须手动加defer r.MultipartForm.RemoveAll(),否则磁盘悄悄被占满 - 防超大请求体得靠
http.MaxBytesReader包裹r.Body,这个和maxMemory是两回事,漏掉就等于没设防
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











