Go 的 http.FileServer 不支持文件上传,仅处理 GET 请求;上传需手动解析 multipart/form-data,调用 ParseMultipartForm 并合理设置 maxMemory,配合 Nginx 限流与临时目录管理。

Go 的 http.FileServer 不能直接用于上传,别踩这个坑
很多刚学 Go Web 开发的人会误以为 http.FileServer 支持文件上传——它只处理 GET 请求返回静态文件,对 POST 或 multipart/form-data 完全无响应。一旦用它接表单上传,浏览器会卡住或返回 405 Method Not Allowed。
真正上传必须手动解析 request.Body,用 request.ParseMultipartForm 提取文件字段。常见错误是没调用该方法就直接访问 request.MultipartForm,结果得到 nil,后续 request.FormFile 报 http.ErrNotMultipart。
- 务必在读取文件前调用
request.ParseMultipartForm(32 (32MB 内存上限,可根据需要调整) - 上传字段名要和 HTML 表单的
name属性严格一致,比如<input type="file" name="avatar">,就得用request.FormFile("avatar") - 记得关闭
multipart.File,否则可能泄漏文件句柄;用defer file.Close()最稳妥
保存上传文件时,os.Create 和 io.Copy 是最简可靠组合
不要用 ioutil.WriteFile (已弃用)或自己循环 Read/Write,既容易出错又难控制进度。标准库的 io.Copy 自动处理缓冲、错误传播和流式写入,配合 os.Create 就够用。
注意路径安全:用户传来的文件名不能直接拼进 os.Create。比如 ../../etc/passwd 会绕过目录限制。必须清洗文件名,至少保留字母、数字、下划线、点号,并强制加扩展名后缀(如 .jpg)。
立即学习“go语言免费学习笔记(深入)”;
- 用
filepath.Base截掉路径部分,再用正则过滤非法字符(^[a-zA-Z0-9_.]+$) - 生成唯一文件名更稳妥,比如
uuid.New().String() + ".png",避免覆盖和冲突 - 目标目录需提前存在,
os.MkdirAll("/uploads", 0755)比os.Mkdir更健壮
下载文件用 http.ServeFile 简单但有风险,推荐 http.ServeContent
http.ServeFile 虽然一行就能返回文件,但它不校验路径合法性,如果 URL 中含 ../,可能泄露服务器任意文件(如 /download?file=../../../etc/shadow)。生产环境必须禁用。
正确做法是先解析请求参数,用 filepath.Join 拼接绝对路径,再用 filepath.Rel 反向验证是否仍在允许目录内。确认安全后,用 http.ServeContent 手动设置 Content-Disposition 和 Content-Type,支持断点续传和条件请求。
- 不要信任
r.URL.Query().Get("file")的原始值,必须做白名单校验或哈希映射(如用文件 ID 查数据库路径) - 设置
w.Header().Set("Content-Disposition", "attachment; filename=\""+filename+"\"")让浏览器触发下载而非渲染 - 用
mime.TypeByExtension推测 MIME 类型,比硬编码"application/octet-stream"更友好
并发上传时,maxMemory 参数影响内存使用和磁盘临时文件行为
ParseMultipartForm(maxMemory int64) 的参数不是“最大上传大小”,而是“内存缓冲阈值”。小于该值的文件全程在内存中处理;超过的部分会写入临时磁盘文件(默认 os.TempDir()),这会带来 I/O 开销和磁盘空间压力。
如果你的应用常收大文件(如 >10MB),设太小会导致频繁落盘;设太大又可能被恶意上传耗尽内存。建议按业务场景分档:头像类设 8MB,文档类设 64MB,并配 nginx 的 client_max_body_size 做前置限制。
- 临时文件路径可通过
os.Setenv("TMPDIR", "/var/tmp/upload")统一指定,避免和系统临时文件混用 - 上传完成后,临时文件不会自动清理,需手动
os.Remove或依赖 Go 的tempfile自动回收(仅限内存不足时创建的文件) - 用
runtime.GC()强制触发垃圾回收对缓解内存压力帮助有限,关键是控制maxMemory和及时关闭资源


















