✅ 推荐方式是先调用 ctx.Attachment() 设置 Content-Disposition: attachment 响应头并指定文件名,再配合 ctx.SendFile() 或 io.Copy() 发送文件内容;仅设头不发文件将返回空响应。

使用 ctx.Attachment() 设置响应头并触发下载
直接调用 ctx.Attachment() 是最常用的方式,它会自动设置 Content-Disposition: attachment 和合适的 Content-Type,浏览器识别后弹出保存对话框。注意:该函数只设置头,不负责读取或写入文件内容,必须配合 ctx.SendFile() 或手动 io.Copy() 使用。
常见错误是只调用 ctx.Attachment() 就返回,结果返回空响应。正确做法是先设头,再发文件:
func downloadHandler(c *fiber.Ctx) error {
filename := "report.pdf"
c.Attachment(filename) // 设置 Content-Disposition 和 Content-Type
return c.SendFile("./data/" + filename) // 实际发送文件
}
如果文件路径来自用户输入,务必校验路径防止目录遍历(如拒绝 ../),否则可能泄露服务器文件。
手动控制 MIME 类型和文件名编码(中文名乱码)
默认 ctx.Attachment() 对含中文的 filename 使用 ISO-8859-1 编码,Chrome/Firefox 可能显示为乱码或截断。解决方法是手动构造 Content-Disposition 头,用 RFC 5987 格式编码:
- 用
url.PathEscape()处理 UTF-8 文件名(注意不是url.QueryEscape()) - 设置头:
Content-Disposition: attachment; filename*=UTF-8''{escaped} - 仍需显式设置
Content-Type,否则 Fiber 可能推断为text/plain
示例:
func downloadCN(c *fiber.Ctx) error {
filename := "测试报告.pdf"
escaped := url.PathEscape(filename)
c.Set("Content-Disposition", "attachment; filename*=UTF-8''"+escaped)
c.Set("Content-Type", "application/pdf")
return c.SendFile("./data/" + filename)
}
大文件下载避免内存溢出(流式传输)
用 c.SendFile() 下载超大文件(如 >100MB)时,Fiber 默认会尝试读入内存再发送,可能 OOM。应改用 io.Copy() 流式转发:
- 打开文件用
os.Open(),确保 deferfile.Close() - 调用
c.Response().ResetBody()清空可能已写入的缓冲 - 手动设置
Content-Length(可选,但推荐)提升客户端体验
示例:
func streamDownload(c *fiber.Ctx) error {
file, err := os.Open("./data/large.zip")
if err != nil {
return c.Status(fiber.StatusNotFound).SendString("file not found")
}
defer file.Close()
stat, _ := file.Stat()
c.Set("Content-Disposition", "attachment; filename=\"large.zip\"")
c.Set("Content-Type", "application/zip")
c.Set("Content-Length", fmt.Sprintf("%d", stat.Size()))
c.Response().ResetBody()
_, err = io.Copy(c.Response().BodyWriter(), file)
return err
}
注意:此时不能用 c.SendFile(),也不能在 io.Copy() 后再调用其他 c.Send* 方法,响应体已被接管。
前端发起下载时避免被拦截或静默失败
从 JavaScript 调用 fetch() 或 axios.get() 获取文件流后,若直接用 window.open() 或 <a href>,多数浏览器会因跨域或缺少 Content-Disposition 拦截下载。可靠方式是创建 Blob + URL.createObjectURL():
- 后端必须返回
Content-Disposition: attachment,且响应允许跨域(Access-Control-Expose-Headers: Content-Disposition) - 前端 fetch 需设
responseType: 'blob',否则 blob 内容为空 - 不要用
location.href直接跳转——这会丢失请求头(如认证 token)
关键前端片段:
fetch("/api/download", {
headers: { Authorization: "Bearer xxx" },
responseType: "blob"
}).then(r => r.blob())
.then(blob => {
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "data.xlsx"; // 此处 filename 仅作 fallback,优先取响应头
a.click();
URL.revokeObjectURL(url);
});
容易忽略的是:Fiber 默认不暴露 Content-Disposition 响应头给前端,需显式配置 CORS 中间件添加 Access-Control-Expose-Headers。


















