Buffalo中文件下载核心是后端用c.Response()手动写二进制流并设Content-Type与Content-Disposition头,前端fetch必须显式配responseType: 'blob',否则JS按文本解析致乱码。

Buffalo 中做文件下载,核心不是“设 responseType”,而是后端用 c.Response() 手动写二进制流 + 正确设置响应头;前端 fetch 必须显式配 responseType: 'blob',否则 JS 会尝试解析成文本,直接乱码。
怎么用 c.Response() 返回真实文件流
Buffalo 的 c.Render() 默认走 JSON/text 渲染管道,不能传原始字节。必须绕过它,直接操作底层 http.ResponseWriter:
- 先调
c.Response().Header().Set("Content-Type", "application/pdf")(别用application/octet-stream,除非真不确定类型) - 加下载头:
c.Response().Header().Set("Content-Disposition", `attachment; filename="report.pdf"`) - 调
c.Response().WriteHeader(200)—— 这步必须在任何Write()前,且只能调一次 - 最后用
c.Response().Write(data)(小文件)或io.Copy(c.Response(), file)(大文件,避免内存拷贝) - 一旦调了
WriteHeader()或Write(),就不能再调c.Render(),否则 panic
代理下游服务文件流时如何透传不丢 header 和 body
常见于 Buffalo 作为 BFF 层,把 Spring Boot / Python 后端的导出接口原样透传给前端。不能自己读全再吐出,否则失去流式能力、吃内存:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 用
http.DefaultClient.Do(req)请求下游,拿到*http.Response - 手动复制关键 header:
for k, vs := range resp.Header { for _, v := range vs { c.Response().Header().Add(k, v) } } - 跳过 hop-by-hop 头:如
Transfer-Encoding、Connection、Keep-Alive,它们不能被中间件转发 - 用
io.Copy(c.Response(), resp.Body)直接转发流,结束后记得resp.Body.Close() - 如果下游返回的是带包装的 JSON(比如
{"code":200,"data":{...}}),那就不是文件流——得先解包再处理,不能直接 copy
前端 fetch 怎么配才能正确接收 blob
Buffalo 后端只管发二进制流和 header,前端必须主动配合,否则浏览器会按默认 text 解析:
立即学习“go语言免费学习笔记(深入)”;
- fetch 时必须加
responseType: 'blob',例如:fetch("/api/export", { responseType: 'blob' }) - 若用 Axios,要设
responseType: 'blob',且注意 Axios v1+ 默认不自动设置Content-Type,需手动加headers: { Accept: 'application/pdf' } - 收到响应后,用
URL.createObjectURL(res.data)创建临时链接,再用a.download触发保存 - 别用
res.text()或res.json()尝试读取 —— 文件流无法转成 JSON,会报错或截断
最容易被忽略的是:下游服务返回的 Content-Type 若是 application/json,但 body 实际是 base64 编码的 PDF,这种“伪文件流”必须在 Buffalo 层 decode 后再写回,不能直接 copy;否则前端拿到的是 JSON 字符串,不是二进制 blob。

















