必须先调用 r.parsemultipartform(32

multipart.Reader 不能直接读 multipart/form-data 请求体
Go 的 http.Request 已经封装了对 multipart/form-data 的解析逻辑,你不需要手动创建 multipart.Reader 去解析原始请求体——那样会跳过 boundary 提取、header 解析等关键步骤,大概率触发 multipart: invalid boundary 或空文件。
正确入口是 r.ParseMultipartForm(),它会预读并缓存整个请求体(或按限制截断),之后才能安全调用 r.MultipartForm。
- 必须先调用
r.ParseMultipartForm(32 (例如 32MB 限制),否则 <code>r.MultipartForm为nil - 若不设上限,恶意上传可能耗尽内存;设太小则大文件直接报错
http: request body too large -
ParseMultipartForm是一次性操作,重复调用无副作用但没必要
file.Header.Size 未必等于实际写入的字节数
上传时客户端可能伪造 Content-Length,file.Header.Size 只是 header 中声明的值,不可信。真正可靠的大小来自 io.Copy 返回的字节数,或读完后用 os.Stat 检查文件长度。
尤其当使用 file.Open() 后再流式写入(比如转存到 S3),别依赖 header size 做校验或限流。
立即学习“go语言免费学习笔记(深入)”;
- 用
io.CopyN(dst, src, maxBytes)更安全地控制单文件写入上限 - 如果需完整读取校验,用
io.ReadAll(file),但注意内存占用 - 上传超时或客户端中断时,
file可能提前 EOF,io.Copy返回的n, err必须检查
FormFile 和 MultipartForm.File 区别在哪
r.FormFile("avatar") 是快捷方式,只取第一个同名文件;而 r.MultipartForm.File["avatar"] 是切片,包含所有同名文件(即支持多选上传)。
如果你允许用户一次选多个文件(<input type="file" multiple>),必须用后者,否则只会收到第一个。
-
FormFile内部会调用ParseMultipartForm,但只返回首个匹配项 -
MultipartForm.File是 map[string][]*multipart.FileHeader 类型,key 是表单字段名 - 遍历时记得检查
len(files) > 0,空切片不报错但没内容
临时文件路径和内存缓冲的隐式切换
Go 默认在 ParseMultipartForm 时,小文件(默认 32KB)进内存,大文件落磁盘临时文件。这个阈值由 MaxMemory 参数控制,不是 maxMemory 参数本身。
这意味着:你用 file.Open() 打开的可能是内存中的 *io.ReadCloser,也可能是磁盘上的 *os.File —— 但对外接口一致。唯一要注意的是临时目录权限和清理。
- 可通过
os.Setenv("TMPDIR", "/path")或设置os.TempDir影响临时位置 - Go 不自动清理这些临时文件,进程退出后靠系统或定时任务回收
- 如需完全避免磁盘 IO,可设
MaxMemory大于单个文件最大预期尺寸(但慎用,防 OOM)
边界情况比想象中多:比如客户端发了个 0 字节文件、filename 为空、content-type 为空、甚至用 curl -F 'file=@/dev/null' 测试——这些都得在 file.Header.Filename != "" 和 file.Size > 0 上做防御性判断。










