
Go 的 http.ServeFile 不能直接用作下载接口
它会强制设置 Content-Type 并忽略自定义头,且不支持断点续传、文件名中文乱码、大文件流式传输等常见需求。真实业务里基本要自己写 handler。
- 浏览器访问
http.ServeFile返回的路径时,文件名默认从 URL 路径截取,中文.txt可能变成%E4%B8%AD%E6%96%87.txt,触发下载失败或乱码 - 它内部调用
http.ServeContent,但你没法控制modtime和size的来源,对生成型文件(如 zip 打包)完全不适用 - 上传侧更不能依赖
http.ServeFile—— 它只处理 GET,压根不碰 POST 或 multipart
multipart.FormFile 是上传入口,但必须手动校验和清理
它只是帮你从请求体里抽出一个 *multipart.FileHeader,后续所有事情都得自己扛:保存路径、大小限制、类型检查、临时文件清理。
- 务必在
r.ParseMultipartForm(32 中设上限(比如 32MB),否则攻击者可发超大表单耗尽内存 -
file, header, err := r.FormFile("file")中的"file"必须和前端<input type="file" name="file">的name严格一致,大小写敏感 -
header.Size是客户端声称的大小,不可信;读取时要用io.CopyN或带限速的io.Copy防止写爆磁盘 - 别忘了
defer file.Close(),否则文件句柄泄漏,Linux 下撑不过几千并发
下载时用 http.ServeContent 而不是 io.Copy
直接 io.Copy(w, f) 会丢失 Last-Modified、ETag、范围请求(Range)支持,导致无法断点续传、CDN 缓存失效、iOS Safari 下载卡死。
- 必须提供准确的
modtime(用f.Stat().ModTime()),否则If-Modified-Since判定永远失效 -
http.ServeContent要求传入一个io.ReadSeeker,普通*os.File满足,但 bytes.Buffer 不行(需包装成bytes.NewReader(buf.Bytes())) - 设置文件名用
w.Header().Set("Content-Disposition", `attachment; filename="`+filename+`"`),注意filename*格式才支持 UTF-8,但兼容性差,建议用 ASCII 文件名 + URL Encode 处理
上传临时文件不清理 = 磁盘迟早被吃光
Go 的 multipart 默认把超过 32KB 的文件先写到 /tmp,即使你没调用 file.Open(),只要解析过表单,临时文件就已存在。
立即学习“go语言免费学习笔记(深入)”;
- 调用
r.MultipartForm后,必须显式执行r.MultipartForm.RemoveAll(),否则临时文件残留 - 不要依赖
defer r.MultipartForm.RemoveAll()—— 如果 handler panic,defer 可能不执行;建议在 handler 结尾无条件调用 - 生产环境建议改默认临时路径:
http.MaxBytesReader控制总请求体大小,os.Setenv("TMPDIR", "/var/tmp/upload")避免和系统其他服务争抢/tmp










