Revel 框架无内置下载 API,需手动设置 Content-Type 和 Content-Disposition 响应头并写入内容;常用方式包括 c.RenderBinary()、io.Copy() 流式传输、http.ServeFile 强制下载静态文件,同时须禁用 CompressFilter 等干扰过滤器,并注意中文文件名编码。

Revel 框架本身不提供封装好的文件下载 API,但它的 HTTP 响应控制能力足够直接、底层——你只需要正确设置 Content-Type 和 Content-Disposition 响应头,并写入二进制或文本内容即可触发浏览器下载行为。关键不是“用什么函数”,而是“怎么构造响应”。
Controller 中返回文件流(最常用)
这是后端生成/读取文件并推送给前端的标准做法,适用于导出 CSV、PDF、Excel 或读取服务器上已存在的静态资源。
核心是调用 c.RenderBinary() 或手动设置响应头 + c.Response.Out.Write():
-
c.RenderBinary()会自动设置Content-Type(可选),但Content-Disposition需要你提前设好:c.Response.Header().Set("Content-Disposition", "attachment; filename=\"report.csv\"") c.RenderBinary([]byte(csvContent), "text/csv") - 如果文件较大或来自磁盘,建议用
io.Copy()避免内存拷贝:file, _ := os.Open("/path/to/report.pdf") defer file.Close() c.Response.Header().Set("Content-Disposition", "attachment; filename=\"report.pdf\"") c.Response.Header().Set("Content-Type", "application/pdf") io.Copy(c.Response.Out, file) - 注意:Revel 默认会对响应体做 gzip 压缩(
CompressFilter),但二进制文件通常不该压缩。可在 action 前加拦截器禁用:revel.InterceptFunc(disableGzip, revel.BEFORE, &YourController.Download)
返回静态文件并强制下载(简单场景)
当文件已存在 public/ 目录下,且你只想让浏览器下载而非打开(比如 PDF 被默认 inline 显示),不能只靠路由映射,必须改响应头。
推荐在 app/controllers/static.go 中新增一个 action:
- 不要直接用 Revel 的静态文件路由(它不支持自定义 header);
- 用
http.ServeFile+ 手动 header 更可控:func (c App) DownloadStatic(filename string) revel.Result { path := filepath.Join(revel.BasePath, "public", filename) if _, err := os.Stat(path); os.IsNotExist(err) { c.Response.Status = 404 return nil } c.Response.Header().Set("Content-Disposition", "attachment; filename=\""+filename+"\"") http.ServeFile(c.Response.Out, c.Request, path) return nil } - 对应路由写成:
GET /download/{filename} App.DownloadStatic
前端触发下载时的跨域与 Token 问题
如果前端是 React/Vue 单页应用,且请求需带认证 header(如 Authorization),就不能用 或 window.open() —— 它们无法携带自定义 header。
此时必须走 Blob + URL.createObjectURL() 流程,后端保持普通 JSON/二进制响应即可:
- Revel 后端无需特殊处理,只要返回正确 content-type 和 body;
- 前端 fetch 时必须设
responseType: 'blob',否则会解析失败; - 常见错误:
Failed to execute 'createObjectURL' on 'URL': Overload resolution failed.—— 说明 fetch 没拿到 blob,检查是否误设了Accept: application/json; - Revel 默认对非 HTML 响应不设
Content-Disposition,所以前端必须自己拼文件名:a.download = "data.xlsx",不能依赖响应头。
真正容易被忽略的是:Revel 的 CompressFilter 和 ValidationFilter 在二进制响应场景下可能干扰流式写入,尤其是大文件分块传输时。建议在下载类 action 上显式跳过这些 filter,而不是全局关掉。另外,Content-Disposition 中的 filename 如果含中文,必须用 filename*=UTF-8''xxx 编码格式,否则 Chrome/Firefox 表现不一致。











