ReadForm 不处理文件上传,仅解析文本字段;正确入口是 ParseMultipartForm、FormFile 或 MultipartReader;maxMemory 控制内存缓存上限,非总大小限制。

ReadForm 不是文件上传的正确入口
ReadForm 是 mime/multipart 包里一个低阶函数,它只解析表单字段(text/plain 类型的 part),**完全不处理文件上传**。如果你在 handler 中调用 r.MultipartReader().ReadForm(n) 或手动构造 multipart.Reader 后调 ReadForm,它会跳过所有 Content-Disposition: form-data; name="file"; filename="x.jpg" 这类 part,导致文件数据被 silently 丢弃或后续解析错位。
真正用于文件上传的入口只有:r.ParseMultipartForm、r.FormFile、r.MultipartReader()。误用 ReadForm 不仅无法拿到文件,还会让未消费的 body 流残留,引发连接卡死、超时或后续请求读取错乱。
ParseMultipartForm 的 maxMemory 参数不是大小限制开关
r.ParseMultipartForm(maxMemory) 的 maxMemory 参数控制的是「内存中缓存的表单字段 + 小文件内容」上限,不是上传总大小限制。设为 32 (32MB)只表示 ≤32MB 的部分放内存,超的部分自动落盘 —— 但客户端仍可发 2GB 文件,服务端照单全收,磁盘可能被填满。
- 它不拦截网络传输,不关闭连接,不返回错误
- 它不校验
Content-Length,也不检查FileHeader.Size - 若未提前设置
http.MaxBytesReader,OOM 风险仍在
必须前置使用 http.MaxBytesReader 截断整个请求体
真正的第一道防线是 http.MaxBytesReader,它必须在任何解析操作前包装 r.Body:
立即学习“go语言免费学习笔记(深入)”;
- 放在
r.ParseMultipartForm、r.FormFile、r.PostForm之前,否则无效 - 限制的是整个请求体字节数(含所有字段 + 所有文件),多文件场景要预留余量(如单文件限 10MB,总请求体设 15MB)
- 触发后 handler 会收到
http.ErrBodyReadAfterClose,客户端通常看到 400 或连接重置 - 示例:
r.Body = http.MaxBytesReader(w, r.Body, 15*1024*1024)
FileHeader.Size 只能做二次校验,且需配合真实类型检测
header.Size 是从 multipart boundary 解析出的声明大小,可被客户端伪造,因此只能作为业务级二次过滤:
- 必须在
r.FormFile调用后立即检查,但此时文件可能已部分加载到内存或磁盘 - 绝不能用
len(io.ReadAll(file))校验——大文件直接 OOM - 扩展名和
Content-Type头都不可信,必须读 magic bytes(如用github.com/h2non/filetype读前 262 字节) - 路径拼接必须防遍历:
filepath.Clean(filename)+strings.HasPrefix(absPath, absUploadDir)
最易被忽略的点:ParseMultipartForm 的调用时机和 MaxBytesReader 的包裹顺序,错一个就等于没设防。


















