最常用且安全的下载方式是c.File(),它自动设置Content-Disposition头并流式传输文件;路径须为绝对或正确相对路径,需校验存在性与权限,禁用用户输入拼接以防路径遍历。

使用 c.File() 直接返回本地文件
这是最常用、最安全的下载方式,适用于已知路径且文件存在服务器本地的场景。Gin 会自动设置 Content-Disposition: attachment 头,并读取文件流返回。
注意:路径必须是绝对路径或相对于可执行文件的正确相对路径;若路径错误,会返回 404 或空响应,而不是报错。
- 推荐用
filepath.Abs()或os.Executable()拼接路径,避免部署时因工作目录不同导致文件找不到 - 不要直接拼接用户输入的文件名(如
c.Param("filename")),否则可能触发路径遍历漏洞(如../../etc/passwd) - 如果文件较大(>100MB),
c.File()仍适用——它底层用http.ServeFile流式传输,不全量加载进内存
示例:
func downloadHandler(c *gin.Context) {
filename := "report.pdf"
filepath := "./downloads/" + filename
// 建议先校验文件是否存在且可读
if _, err := os.Stat(filepath); os.IsNotExist(err) {
c.AbortWithStatus(404)
return
}
c.Header("Content-Description", "File Transfer")
c.Header("Content-Transfer-Encoding", "binary")
c.Header("Content-Disposition", "attachment; filename="+filename)
c.File(filepath)
}
用 c.Data() 返回内存中生成的文件内容
适合动态生成文件(如 Excel、PDF、ZIP)后不落地磁盘,直接返回给前端。此时你掌握完整字节流,能精确控制 Content-Type 和文件名。
关键点在于:必须手动设置 Content-Disposition 头,否则浏览器可能内联渲染(如 PDF 在页内打开),而非下载。
-
c.Data()第一个参数是 HTTP 状态码(通常200),第二个是Content-Type(如"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"),第三个是字节切片[]byte - 文件名需通过
c.Header("Content-Disposition", "attachment; filename=xxx.xlsx")显式指定,注意 URL 编码中文名(用url.PathEscape()) - 如果生成逻辑耗时,建议加超时或异步处理,避免阻塞 HTTP 连接
示例(生成 CSV 字符串后下载):
data := "name,age\nAlice,30\nBob,25"
c.Header("Content-Type", "text/csv; charset=utf-8")
c.Header("Content-Disposition", "attachment; filename=data.csv")
c.Data(200, "text/csv; charset=utf-8", []byte(data))
处理大文件下载时的超时与断点续传
Gin 默认不启用 Range 请求支持,所以原生 c.File() 不支持断点续传(即无法暂停/继续)。若需该能力,得手动实现 http.ServeContent 逻辑。
更现实的做法是:对超大文件(如 >1GB),别走 Gin 路由处理,改用 Nginx 静态文件服务 + X-Accel-Redirect,让 Gin 只做权限校验和跳转。
- 若坚持用 Gin,可用
http.ServeContent()替代c.File(),但需自己处理If-Modified-Since、Range、Etag等逻辑,复杂度陡增 - Gin 的默认读写超时是 30 秒,下载大文件易超时,需在
gin.Engine初始化后调整:r.MaxMultipartMemory = 8 (仅影响上传),而下载超时由http.Server.ReadTimeout/WriteTimeout控制 - 生产环境强烈建议把大文件托管给对象存储(如 S3)并签发预签名 URL,由浏览器直连下载,彻底绕过 Go 进程
中文文件名乱码或被截断
浏览器对 Content-Disposition 中的中文 filename 解析不一致:Chrome 支持 filename*=UTF-8''xxx 编码格式,Safari 和旧版 IE 则只认 filename 的 ASCII 子集。单一写法无法兼容全部。
稳妥方案是:优先用英文名 fallback,或只在 filename* 中提供编码值,同时保留 ASCII 的 filename(如 download.pdf)。
- 用
mime.WordEncode("utf-8", "报表.xlsx")生成符合 RFC 2231 的编码字符串(需import "mime") - 构造 header 时,同时写两个字段:
c.Header("Content-Disposition", "attachment; filename=\"download.xlsx\"; filename*=UTF-8''%E6%8A%A5%E8%A1%A8.xlsx") - 测试时务必在 Chrome、Firefox、Safari 和 Edge 下分别验证,尤其注意 Safari 对
filename*的支持较晚(iOS 13+)
真正容易被忽略的是:一旦设置了 filename*,部分老客户端会完全忽略 filename 字段,所以 fallback 名必须有意义,不能是空或随机字符串。











