用 http.HandleFunc 接收 multipart/form-data 文件需先调用 r.ParseMultipartForm 设置 MaxMemory,再通过 r.FormFile 或 r.MultipartForm.File 获取文件;不可直接读 r.Body 或用 r.FormValue 取文件内容;须校验路径、内存、超时和 MIME 类型以防 RCE、DoS 或信息泄露。

如何用 http.HandleFunc 接收 multipart/form-data 文件
Go 标准库原生支持表单文件上传,关键不是自己解析 raw body,而是调用 r.ParseMultipartForm 触发解析,再从 r.MultipartForm.File 中取文件元数据。不调用这个方法,r.FormFile 会返回 http.ErrNotMultipart。
常见错误是直接读 r.Body 或误以为 r.FormValue 能拿到文件内容 —— 它只能取文本字段。
- 必须设置
MaxMemory(如32 表示 32MB 内存缓冲),否则大文件会直接写临时磁盘,且无错误提示 - 前端
必须在内,否则后端收不到文件 -
r.FormFile("file")返回的是*multipart.FileHeader,它不包含文件内容,只是描述信息(如Fh.Filename、Fh.Size、Fh.Header)
如何安全地保存上传的文件到本地磁盘
拿到 *multipart.FileHeader 后,需用 Fh.Open() 打开源流,再用 os.Create 创建目标文件并 io.Copy。重点在于路径拼接和文件名处理 —— 直接使用 Fh.Filename 是严重安全隐患。
- 永远不要用
Fh.Filename作为最终文件名:它可被客户端伪造为../../etc/passwd或带 null 字节 - 推荐做法:生成唯一 ID(如
uuid.New().String()),再根据原始扩展名白名单校验后拼接,例如fmt.Sprintf("%s%s", id, ext),其中ext来自path.Ext(Fh.Filename)且仅限.jpg、.pdf等可信后缀 - 保存前检查目标目录是否存在,用
os.MkdirAll(dir, 0755)创建;避免用os.Create写入系统敏感路径(如/tmp下未隔离的子目录)
为什么 http.ServeFile 不该直接暴露上传目录
上传后的文件若需通过 HTTP 访问,很多人会直接注册 http.Handle("/uploads/", http.StripPrefix("/uploads/", http.FileServer(http.Dir("./uploads")))) —— 这会导致路径遍历风险:只要 URL 中含 %2e%2e/(即 ../ 编码),就可能访问到上级目录。
立即学习“go语言免费学习笔记(深入)”;
-
http.FileServer默认不做路径规范化校验,http.Dir的路径若为相对路径(如"./uploads"),攻击者可构造/uploads/..%2f/etc/passwd绕过 - 正确做法:用
http.FileServer配合自定义FileSystem,或改用显式http.HandlerFunc+http.ServeContent,先校验请求路径是否匹配预设正则(如^/[a-f0-9]{32}(\.[a-z]{2,4})?$),再打开对应文件 - 更稳妥方案:将文件存入独立服务(如 MinIO)或 CDN,Web 服务只负责上传和生成带签名的临时 URL
上传大文件时如何避免内存溢出和超时
默认 http.Server 的 ReadTimeout 和 WriteTimeout 很短(通常 30 秒),而大文件上传可能耗时数分钟;同时 ParseMultipartForm 的 MaxMemory 若设得过大,会导致高并发时内存暴涨。
- 在
http.Server初始化时显式设置ReadTimeout: 5 * time.Minute,并禁用WriteTimeout(改用IdleTimeout控制空闲连接) -
MaxMemory建议设为 32MB 左右:再大易 OOM,再小则频繁落盘影响性能;可通过Fh.Size在解析前做快速拦截(如 >500MB 直接http.Error(r, "file too large", http.StatusBadRequest)) - 生产环境务必加中间件记录上传进度(如用
io.TeeReader+ 自定义WriteTo),否则无法区分是网络卡顿还是服务假死
文件上传看着简单,但路径校验、内存控制、超时配置、MIME 类型验证这四点,任一遗漏都可能引发 RCE、DoS 或信息泄露 —— 尤其是 Fh.Filename 和 http.Dir 的组合,最容易被忽略。










