Go文件下发核心是安全可控地流式写入ResponseWriter:http.ServeFile虽快但不安全,手动io.Copy+os.Open更稳;需路径净化、白名单校验、正确设Header(Content-Disposition/Content-Length)、处理中断与断点续传。

直接说结论:Go 语言做文件下发(即服务端把文件发给 HTTP 客户端),核心不是“怎么读文件”,而是「怎么安全、可控、兼容地把文件流式写进 ResponseWriter」。用 http.ServeFile 最快,但不安全;手动 io.Copy + os.Open 最稳,但得自己管头、状态码和中断。
为什么 http.ServeFile 要慎用
它一行代码就能返回文件,但默认不做路径净化,用户传 ../../etc/passwd 就能读任意系统文件。
- 必须先用
filepath.Clean()规范路径,再用strings.HasPrefix()检查是否落在白名单目录内(比如/var/www/files) - 它不支持
Range请求,大文件断点续传直接失效 - 无法在写入前加鉴权逻辑(比如检查 JWT 或 session)
- 中文文件名默认不生效,
Content-Disposition得手动设,且要同时提供filename和filename*两套格式
手动 io.Copy 是生产首选
绕过 http.ServeFile,自己打开文件、设置响应头、流式写入——控制力强,也更贴近真实业务场景(如下载前记录日志、限速、动态生成)。
- 先调
f, err := os.Open(path),出错立即http.Error(),别等后面写 header 时才发现文件不存在 - 用
w.Header().Set("Content-Length", strconv.FormatInt(fi.Size(), 10))提前设大小,让浏览器进度条可算(但压缩响应或动态内容就别设) -
Content-Disposition必须在w.WriteHeader()前设置,否则无效;中文名推荐:url.PathEscape("报表.xlsx")后拼进filename*=UTF-8''... - 最后用
io.Copy(w, f),不是io.Copy(f, w)——顺序反了会写到文件里去
大文件传输必须处理连接中断
客户端中途关浏览器、网络闪断,io.Copy 会返回 broken pipe 或 connection reset 错误,但 Go 默认不主动感知,容易卡住 goroutine 或漏记日志。
立即学习“go语言免费学习笔记(深入)”;
- 启用
http.Server{ReadTimeout: 30 * time.Second, WriteTimeout: 5 * time.Minute}防卡死 - 对超大文件(>1GB),考虑加
http.TimeoutHandler包一层,避免单请求拖垮整个服务 - 若需断点续传支持,服务端得响应
Accept-Ranges: bytes头,并在Range请求时返回206 Partial Content+Content-Range,这已超出http.ServeFile能力范围,必须手写逻辑
真正难的不是“怎么发出去”,而是“怎么知道它发没发完、发到哪了、中间断了怎么办”。Header 设置时机、错误返回顺序、连接生命周期管理——这些细节一错,文件就下一半、名字乱码、或者干脆 404 了。


















