文件下载必须用 ctx.ServeContent 或 ctx.SendFile,不能 return 字符串或结构体;需校验路径防穿越、禁用 Gzip 压缩、手动设置 Content-Disposition 处理中文名。

文件下载必须用 ctx.ServeContent 或 ctx.SendFile,不能 return 字符串或结构体
控制器方法返回值会被 Iris 自动 JSON 序列化,直接 return 文件内容会导致响应头错乱、浏览器无法识别为下载。必须显式调用上下文方法触发文件传输流程。
常见错误现象:Content-Type: application/json 被强制设置、响应体是 base64 编码字符串、浏览器弹出空白页或报解析错误。
-
ctx.SendFile(path):适合已知本地绝对路径的静态文件,自动设置Content-Disposition: attachment和 MIME 类型 -
ctx.ServeContent(name, modTime, contentLength, reader):适合动态生成内容(如 zip 流、加密后数据),需手动控制文件名、时间戳和长度 - 若路径来自用户输入(如
/download/{filename}),务必校验filename是否含../,否则可能触发目录穿越
BeforeActivation 中注册下载路径时,路径参数类型要匹配 string
比如想支持 /download/report.xlsx,路由定义必须写 {filename:string},不能只写 {filename} 或 {filename:long}——后者会导致 ctx.Params().Get("filename") 返回空字符串,最终 SendFile 传入空路径而 panic。
正确写法示例:
func (c *DownloadController) BeforeActivation(b mvc.BeforeActivation) {
b.Handle("GET", "/download/{filename:string}", "DownloadFile")
}对应方法签名必须接收 filename string 参数:
func (c *DownloadController) DownloadFile(ctx iris.Context, filename string) {
// 校验 filename 合法性
if strings.Contains(filename, "..") || !strings.HasSuffix(filename, ".xlsx") {
ctx.StatusCode(iris.StatusForbidden)
ctx.WriteString("Invalid filename")
return
}
fullPath := filepath.Join("./downloads", filename)
ctx.SendFile(fullPath)
}大文件下载要禁用 Gzip 压缩,否则内存暴涨或超时
Iris 默认启用 iris.Compression 中间件,对二进制文件做 gzip 压缩不仅无效,还会导致整个文件读入内存再压缩,极易 OOM 或触发 context deadline exceeded。
解决方案分两层:
- 全局禁用:移除
app.Use(iris.Compression),改用按路径白名单启用(如只对.html、.js压缩) - 局部绕过:在下载方法开头加
ctx.StopExecution(),阻止后续中间件执行;或在SendFile前调用ctx.Header("Content-Encoding", "")清空编码头 - 更稳妥的做法是给下载路由单独开一个
Party,不挂载压缩中间件:dl := app.Party("/download"); dl.Use(/* 其他中间件 */); mvc.New(dl).Handle(new(DownloadController))
文件名中文乱码?用 ctx.Header 手动设 Content-Disposition
ctx.SendFile 对中文文件名默认使用 utf-8 编码,但部分旧版 Chrome 和 Safari 会当成 iso-8859-1 解析,显示为方块或乱码。
解决方式是绕过 SendFile 的自动头设置,改用 ServeContent 并手动构造标准 RFC 5987 格式头:
func (c *DownloadController) DownloadCN(ctx iris.Context, filename string) {
f, _ := os.Open("./downloads/" + filename)
defer f.Close()
stat, _ := f.Stat()
<pre class="brush:php;toolbar:false;">ctx.Header("Content-Type", "application/octet-stream")
ctx.Header("Content-Disposition",
`attachment; filename*=UTF-8''`+url.PathEscape(filename))
ctx.ServeContent(filename, stat.ModTime(), stat.Size(), f)}
注意:url.PathEscape 是必须的,它把中文转成 %E4%BD%A0%E5%A5%BD.xlsx 格式,浏览器才能正确还原。
这个环节最容易被忽略:开发时用英文名测试通过,上线后用户一下载中文名就失败,问题根源往往就在这里。


















