应使用 ctx.sendfile 配合手动设置 content-disposition 实现可控文件下载,避免 ctx.servefile 的硬编码限制;大文件或需鉴权时改用 io.copy 流式传输,并注意中文名兼容与 nginx 透传配置。

ctx.ServeFile 会强制设置 Content-Disposition,不适用于动态文件名
直接调用 ctx.ServeFile("/path/to/file.pdf") 确实能触发浏览器下载,但它内部硬编码了 Content-Disposition: attachment; filename="file.pdf",无法按需改写文件名(比如加时间戳、用户ID)。一旦真实文件路径含中文或特殊字符,还可能被浏览器截断或乱码。
- 浏览器实际收到的响应头里
filename是原始文件名,不是你期望的下载名 - 如果文件路径是变量拼接(如
/uploads/20260929/zhangsan_report.pdf),ServeFile仍只取最后一段作为filename,无法注入业务逻辑 - 它不支持流式传输大文件,内存占用高,容易 OOM
用 ctx.SendFile + 自定义 Header 才可控
真正可落地的方式是组合 ctx.SendFile 和手动设置 Content-Disposition。注意:必须在 SendFile 前调用 ctx.Header,否则会被框架覆盖。
-
ctx.Header("Content-Disposition", `attachment; filename="report_20260929.pdf"`)—— 双引号包裹,英文名直接写;中文名需用filename*=UTF-8''...编码格式 -
ctx.SendFile("/real/path/on/disk/report.pdf")—— 路径必须是服务端绝对路径或相对于可执行文件的相对路径,不能是 URL - 若文件不存在,
SendFile默认返回 404,无需额外判断,但建议加os.Stat预检避免日志刷屏
示例:
app.Get("/download/{id:string}", func(ctx iris.Context) {
id := ctx.Params().Get("id")
realPath := "/data/reports/" + id + ".pdf"
if _, err := os.Stat(realPath); os.IsNotExist(err) {
ctx.StatusCode(404)
ctx.WriteString("file not found")
return
}
// 注意:Header 必须在 SendFile 前设置
ctx.Header("Content-Disposition", `attachment; filename="report_`+id+`.pdf"`)
ctx.SendFile(realPath)
})
大文件或需要权限校验时,用 ctx.Response().WriteHeader + io.Copy
当下载前要查数据库鉴权、记录日志、或文件超 100MB 时,SendFile 的简单封装就不够用了。此时应绕过框架自动处理,直操作 ResponseWriter。
- 先调
ctx.StatusCode(200)和ctx.ContentType("application/pdf"),再手动写Content-Disposition - 用
io.Copy(ctx.Response(), file)流式转发,内存恒定约 32KB,不随文件大小增长 - 务必用
defer file.Close(),否则句柄泄漏,Linux 下最多打开 1024 个文件就卡死 - 如果文件来自 HTTP 远程源(如 OSS),别用
io.Copy直接转,要加ctx.Request().Context()透传取消信号,防请求中断后 goroutine 泄漏
中文文件名在不同浏览器表现不一致
Chrome 和 Edge 支持 filename*=UTF-8''xxx 格式,Safari 和旧版 Firefox 只认 filename 的 ASCII 子集。最稳方案是 fallback:同时提供两个字段。
ctx.Header("Content-Disposition", `attachment; filename="report.pdf"; filename*=UTF-8''%E6%8A%A5%E5%91%8A.pdf`)- 其中
%E6%8A%A5%E5%91%8A是 “报告” 的 UTF-8 URL 编码,可用url.PathEscape生成 - 不要用
mime.BEncoding.Encode,Iris 不识别,且部分浏览器解析失败
真正容易被忽略的是:Nginx 作为反向代理时默认会删掉带 * 的 header,必须显式配置 proxy_pass_request_headers on; 并在 location 块里加 add_header Content-Disposition $sent_http_content_disposition; 才能透传成功。











