应使用 response()->stream() 实现流式下载,避免 response()->download() 或 storage::download() 导致内存溢出;需手动设置响应头、关闭缓冲、分块读取并 flush;注意 web 服务器(如 nginx)配置禁用缓冲。

用 response()->stream() 实现可控的文件流下载
直接返回大文件时,别用 response()->download(),它会把整个文件读进内存再吐出去,100MB 的 Excel 就可能 OOM。真正要“流式传输”,得自己接管输出缓冲和分块读取逻辑。
关键点在于:手动设置响应头、关闭 Laravel 的自动缓冲、按 chunk 读取并 flush 到客户端。
-
ob_end_clean()必须调用,否则中间件或视图引擎残留的输出缓冲会导致 headers already sent 错误 - 每次
fread()后立即echo+flush(),但要注意 PHP-FPM 默认禁用fastcgi_finish_request级别的 flush,Nginx 配置里得关掉fastcgi_buffering off - 务必设置
Content-Transfer-Encoding: binary和X-Accel-Buffering: no(Nginx)或Cache-Control: no-cache,不然代理或浏览器可能缓存/截断流
return response()->stream(function () {
$file = fopen('/path/to/big.log', 'rb');
while (!feof($file)) {
echo fread($file, 8192);
flush();
usleep(1000); // 防止压垮连接
}
fclose($file);
}, 200, [
'Content-Type' => 'application/octet-stream',
'Content-Disposition' => 'attachment; filename="big.log"',
'X-Accel-Buffering' => 'no',
]);
Laravel 中 Storage::download() 的隐藏限制
这个方法看着省事,但它底层调用的是 response()->download(),意味着文件必须先被 readfile() 或 file_get_contents() 加载——对本地磁盘还行,对 S3 或 SFTP 这类远程驱动就危险了。
比如你用 S3 驱动调 Storage::disk('s3')->download('report.pdf'),Laravel 会先把整个 PDF 从 S3 拉到临时目录再传给浏览器,既慢又占磁盘空间。
- 仅适用于小文件(local 的场景
- 不支持自定义 HTTP 头(如
Content-Range),无法做断点续传 - 如果用了
url()生成预签名链接,别再套一层download(),那只是重定向,不是流式响应
如何安全地响应远程文件(S3 / FTP)而不落地
核心思路:别让 Laravel 做中转,让它当“跳板”,把客户端引向真实资源地址,或用 stream_wrapper 按需拉取。
- 对公有 S3 文件,直接返回 302 重定向到预签名 URL:
return redirect()->away($s3Client->createPresignedRequest(...)->getUri()) - 对私有文件且必须走后端鉴权,用
Storage::disk('s3')->readStream($path)获取 resource,再喂给response()->stream(),避免全量加载 - 注意
readStream()在某些旧版 Flysystem 驱动里不支持 seek,所以没法做 Range 请求;若需支持断点续传,得自己实现Content-Range解析和分段读取
浏览器下载失败?检查这三处 headers 和连接状态
常见现象是进度条卡住、文件大小为 0、或者提示“网络错误”,往往不是代码问题,而是 headers 冲突或连接提前中断。
- 确保没其他中间件(比如 CORS 中间件)重复设置
Content-Length,流式响应不该设这个头 - PHP 超时设置(
max_execution_time)要大于文件传输预期耗时,否则脚本中断导致连接关闭 - 如果用 Nginx,确认
proxy_buffering off和proxy_max_temp_file_size 0,否则它会把流攒成大块再发,破坏实时性
流式响应不是“写完就能跑”,每个环节都可能吃掉 chunk 或丢掉 flush —— 最容易被忽略的是 Web 服务器层的缓冲策略,它比 PHP 代码更常成为瓶颈。









