c.datafromreader 是生产环境下载大文件的默认做法,需准确传入 size 参数、正确编码中文文件名、提前设置完整响应头且顺序严格,避免内存溢出与前端异常。

c.DataFromReader 必须传准确的 size 参数
HTTP 协议强制要求 Content-Length 与实际传输字节数完全一致,否则浏览器进度条卡死、断点续传失效,iOS Safari 会直接拒绝加载。Gin 的 c.DataFromReader 不会帮你校验或补全这个值,传错就出问题。
- 从磁盘读文件:用
os.Stat().Size(),别用len(buf)或估算值 - 从生成器(如 CSV 流)来:必须提前知道总大小;否则得放弃
c.DataFromReader,改用手动分块 +Transfer-Encoding: chunked(但 Gin 不原生支持,需自己写 header 和分块逻辑) - 底层
io.Reader不支持io.Seeker(比如gzip.NewReader包裹的流):size 错误会导致文件截断,且无任何错误提示
中文文件名必须用 url.PathEscape 编码
Content-Disposition 的 filename= 字段只接受 URI path 编码格式,url.QueryEscape 把空格转成 +,Chrome 会解析失败并回退到乱码名;filename*=UTF-8'' 在旧版 Edge 和部分安卓 WebView 上兼容性差,不推荐。
- 正确写法:
c.Header("Content-Disposition", "attachment; filename=\""+url.PathEscape("报告-2026.xlsx")+"\"") - 不要拼接用户原始输入的
filename,哪怕已过滤..——先做白名单校验(如只允许字母、数字、下划线、短横线),再编码
响应头必须在第一次写入前全部设好
Gin 的 c.Header() 只在第一次 Write() 前有效,一旦 c.Writer 开始写 body,再调 c.Header() 就静默失效(也不报错)。顺序错了,Content-Type 或 Content-Disposition 就丢了。
- 关键头至少包括:
Content-Type、Content-Disposition、Content-Length - 顺序不能错:先
c.Header(),再c.DataFromReader() - 别在中间加日志或权限校验逻辑后才设头——那些操作必须放在设头之前
为什么不能用 c.File() 下载大文件
c.File() 底层调用 http.ServeFile,会把整个文件读进内存再发出去。100MB 文件占 100MB 内存,100 并发就是 10GB —— 不是慢,是直接 OOM。它还不支持流控、断点续传,权限校验滞后,错误也静默(比如路径不对返回 404,权限不足返回 500,都没日志)。
- 常见错误现象:
runtime: out of memory或响应超时后连接被 Nginx/ALB 重置 -
c.DataFromReader不是“替代方案”,而是生产环境下载大文件的默认做法 - 别指望靠调大
MaxMultipartMemory解决——那是上传限制,和c.File()无关











