Go 标准库 http 包原生支持文件上传下载;需先调用 req.ParseMultipartForm(32

Go 标准库的 http 包完全支持文件上传与下载,无需第三方依赖;关键在于正确处理 multipart/form-data 请求体和设置响应头。
用 req.ParseMultipartForm 解析上传文件
浏览器表单提交文件时,Content-Type 是 multipart/form-data,必须先调用 ParseMultipartForm 才能访问 req.MultipartForm。否则 req.FormFile 会返回 http.ErrNotMultipart。
常见错误是忽略参数:该方法第一个参数是最大内存缓存字节数(如 32 表示 32MB),超过部分会写入临时磁盘文件。设为 0 会导致解析失败。
- 推荐设为合理上限,例如
req.ParseMultipartForm(32 - 调用后通过
req.FormFile("file")获取*multipart.FileHeader -
header.Filename是客户端原始文件名,不可直接拼路径,需校验或重命名 - 用
file, err := header.Open()得到可读流,再用io.Copy写入本地文件
用 http.ServeFile 或手动 io.Copy 实现安全下载
http.ServeFile 简单但危险:它会自动解析 URL 路径,若用户传 ../../etc/passwd 可能触发目录穿越。生产环境应避免直接使用。
立即学习“go语言免费学习笔记(深入)”;
更稳妥的方式是:固定文件根目录,用 filepath.Join 拼接并校验路径是否在允许范围内。
- 用
filepath.Clean规范路径,再用strings.HasPrefix检查是否仍在白名单目录内 - 设置响应头:
w.Header().Set("Content-Disposition", "attachment; filename=\""+filename+"\"") - 设置
Content-Type:未知类型可用mime.TypeByExtension(filepath.Ext(filename)) - 最后用
io.Copy(w, file)流式传输,避免大文件内存溢出
上传时注意 MaxBytesReader 防止请求体过大
即使设置了 ParseMultipartForm 的上限,攻击者仍可能发送超长请求头或恶意构造的 multipart 边界,导致内存耗尽。应在 handler 开头加一层限制。
- 用
http.MaxBytesReader(w, r, maxUploadSize)包裹原始*http.Request.Body - 例如
maxUploadSize = 100 (100MB),超出则返回413 Payload Too Large - 这个限制在
ParseMultipartForm之前生效,是第一道防线 - 注意:该包装器只限制整个请求体大小,不区分文件字段和其他表单字段
下载时别漏掉 Content-Length 和缓冲策略
缺失 Content-Length 会导致某些客户端(如 curl、wget)无法显示进度,浏览器也可能无法正确识别文件大小。虽然 io.Copy 能工作,但显式设置长度更规范。
- 用
stat, _ := file.Stat()获取文件大小,然后w.Header().Set("Content-Length", strconv.FormatInt(stat.Size(), 10)) - 对于超大文件(>1GB),考虑用
http.ServeContent替代io.Copy,它支持范围请求(Range)、ETag 和条件 GET - 不要用
ioutil.ReadAll读取整个文件进内存——这是最常被忽略的性能陷阱
文件上传下载看似简单,但边界校验、路径安全、流控和响应头细节稍有疏忽就可能引发漏洞或服务异常。尤其是 filepath.Join + filepath.Clean 组合做路径白名单,以及 MaxBytesReader 的前置保护,这两处最容易被跳过。










