HTTP响应头需设Content-Disposition为attachment模式并正确编码中文文件名,配合适当Content-Type;大文件须流式传输并校验路径、处理Range请求;实际路由中须补全路径净化、错误检查、超时控制等安全细节。

HTTP响应头必须设置Content-Disposition
不设这个头,浏览器会尝试渲染文件(比如PDF、JSON),而不是下载。关键是要用attachment模式,并正确编码中文文件名:
-
w.Header().Set("Content-Disposition", "attachment; filename=\""+filename+"\"")—— 仅适用于ASCII文件名 - 中文名需用
filename*=UTF-8''格式,例如:w.Header().Set("Content-Disposition", "attachment; filename*=UTF-8''"+url.PathEscape(filename)) - 别漏掉
Content-Type,用http.DetectContentType或根据扩展名查表更稳妥,避免硬写"application/octet-stream"导致移动端无法识别
文件读取不能直接 ioutil.ReadFile
大文件(>10MB)用ioutil.ReadFile会吃光内存,应流式传输:
- 用
os.Open打开文件,再调用io.Copy(w, f),让内核负责缓冲 - 务必在
defer f.Close()前检查os.Open错误,否则空指针panic - 如果文件路径来自用户输入(如
r.URL.Query().Get("file")),必须做路径净化,防止../../../etc/passwd越界访问
支持断点续传要处理Range请求
浏览器下载中断后重试时会发Range: bytes=1000-,服务端不响应会导致重下整个文件:
- 检查
r.Header.Get("Range")是否非空 - 解析出
start偏移,用f.Seek(start, io.SeekStart) - 设置
206 Partial Content状态码和Content-Range头,例如:bytes 1000-9999/10000 - 注意:
Content-Length此时应为剩余字节数,不是整个文件大小
实际路由示例中容易忽略的细节
以下代码片段看似能跑,但上线后可能出问题:
简单实用原生js实现带缩略图文字说明左右轮播切换相册插件下载。一款基于原生JavaScript实现图片相册幻灯轮播图特效插件,没有引入任何jQuery库,短短数行原生php中文网实现。
立即学习“go语言免费学习笔记(深入)”;
func downloadHandler(w http.ResponseWriter, r *http.Request) {
filename := r.URL.Query().Get("name")
// ❌ 没校验filename是否为空、是否含非法字符
// ❌ 没做路径拼接防护(直接 filepath.Join("./uploads", filename) 危险!)
// ❌ 没检查文件是否存在,stat失败后直接Copy会500
f, err := os.Open(filepath.Join("./uploads", filename))
if err != nil {
http.Error(w, "not found", http.StatusNotFound)
return
}
defer f.Close() // ❌ 这里Close可能被跳过(比如上面error没return)
w.Header().Set("Content-Disposition", "attachment; filename="+filename)
io.Copy(w, f) // ❌ 大文件卡住连接,没超时控制
}
真实项目里,文件路径解析、MIME探测、流控超时、日志记录都得补全,尤其是io.Copy前最好加http.MaxBytesReader防恶意大文件攻击。









