Php 从Laravel存储下载,无需在内存中加载整个文件
我正在使用Laravel存储,我想为用户提供一些(大于内存限制的)文件。我的代码的灵感来自于一个in-SO,如下所示:Php 从Laravel存储下载,无需在内存中加载整个文件,php,laravel,Php,Laravel,我正在使用Laravel存储,我想为用户提供一些(大于内存限制的)文件。我的代码的灵感来自于一个in-SO,如下所示: $fs = Storage::getDriver(); $stream = $fs->readStream($file->path); return response()->stream( function() use($stream) { fpassthru($stream); }, 200, [
$fs = Storage::getDriver();
$stream = $fs->readStream($file->path);
return response()->stream(
function() use($stream) {
fpassthru($stream);
},
200,
[
'Content-Type' => $file->mime,
'Content-disposition' => 'attachment; filename="'.$file->original_name.'"',
]);
不幸的是,我遇到了一个大文件错误:
[2016-04-21 13:37:13] production.ERROR: exception 'Symfony\Component\Debug\Exception\FatalErrorException' with message 'Allowed memory size of 134217728 bytes exhausted (tried to allocate 201740288 bytes)' in /path/app/Http/Controllers/FileController.php:131
Stack trace:
#0 /path/vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php(133): Symfony\Component\Debug\Exception\FatalErrorException->__construct()
#1 /path/vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php(118): Illuminate\Foundation\Bootstrap\HandleExceptions->fatalExceptionFromError()
#2 /path/vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php(0): Illuminate\Foundation\Bootstrap\HandleExceptions->handleShutdown()
#3 /path/app/Http/Controllers/FileController.php(131): fpassthru()
#4 /path/vendor/symfony/http-foundation/StreamedResponse.php(95): App\Http\Controllers\FileController->App\Http\Controllers\{closure}()
#5 /path/vendor/symfony/http-foundation/StreamedResponse.php(95): call_user_func:{/path/vendor/symfony/http-foundation/StreamedResponse.php:95}()
#6 /path/vendor/symfony/http-foundation/Response.php(370): Symfony\Component\HttpFoundation\StreamedResponse->sendContent()
#7 /path/public/index.php(56): Symfony\Component\HttpFoundation\Response->send()
#8 /path/public/index.php(0): {main}()
#9 {main}
它似乎试图将所有文件加载到内存中。我原以为使用stream和passthru不会做到这一点。。。我的代码中缺少什么吗?我是否必须以某种方式指定块大小或什么
我使用的版本是Laravel 5.1和PHP 5.6。不要一次将整个文件加载到内存中,而是尝试使用fread逐块读取和发送它 这是一篇非常好的文章: 在回复之前 归功于
您可以尝试直接使用StreamdResponse组件,而不是Laravel包装器
X-Send-File
X-Send-File
是一个内部指令,包含Apache、nginx和lighthttpd的变体。它允许您完全跳过通过PHP分发文件,它是一条指令,告诉Web服务器作为响应而不是来自FastCGI的实际响应发送什么
我以前在一个个人项目中处理过这个问题,如果你想看到我的工作的总和,你可以在这里访问:这不仅涉及分发文件,还涉及处理流媒体搜索。您可以自由使用该代码 以下是关于
X-Send-File
您必须编辑您的Web服务器并将特定目录标记为内部目录,以便nginx遵守
X-Send-File
指令
我在这里为我的上述代码提供了Apache和nginx的示例配置。这已经在高流量网站上进行了测试。不要通过PHP守护进程来缓冲介质,除非你的站点几乎没有流量或者你正在消耗资源。看来输出缓冲仍在内存中大量积累 在执行fpassthru之前尝试禁用ob:
function() use($stream) {
while(ob_get_level() > 0) ob_end_flush();
fpassthru($stream);
},
可能有多个输出缓冲区处于活动状态,这就是为什么需要while。
我能想到的唯一一种将fpassthru
分配到内存中的场景是使用输出缓冲时。因此,您可以尝试在fread
上使用echo
进行循环。我很想实现这一点,但我不确定安全性。您能否解释一下使用X-Send-File
是否会增加将文件暴露给未经授权的客户端的风险?您可以使用控制器策略,这就是为什么我如此喜欢该解决方案的原因。但是,您应该知道,nginx和潜在的cdn(如Cloudflare)可能会缓存文件并将其分发给拥有URL的任何人。这正是fpasstru的用途,不需要使事情复杂化。我不这么认为。。我做了一个实验,fpassthru
产生了完全相同的错误。通过这种方法,我可以下载文件。@Christiaan我已经更新了答案中的代码,你可以在你的计算机上做这个实验。(只需生成一个20GB的大文件)使用fpasstru,您是否确保禁用了输出缓冲?因为这就是你的例子每次调用flush都会失败的原因。@Christiaan你是对的。。谢谢你指出这一点。是的,这其实是一个很简单的问题,我怎么可能没有抓住要点。只要调用if(ob_get_level())ob_end_clean()返回响应前的代码>。我将更新答案,并给予你信任。该答案解决了导致我尝试实施中出现问题的实际问题,因此我接受并奖励你奖金。感谢大家的其他回复,这些回复也是宝贵的信息!
if (ob_get_level()) ob_end_clean();
//disable execution time limit when downloading a big file.
set_time_limit(0);
/** @var \League\Flysystem\Filesystem $fs */
$fs = Storage::disk('local')->getDriver();
$fileName = 'bigfile';
$metaData = $fs->getMetadata($fileName);
$stream = $fs->readStream($fileName);
if (ob_get_level()) ob_end_clean();
return response()->stream(
function () use ($stream) {
fpassthru($stream);
},
200,
[
'Content-Type' => $metaData['type'],
'Content-disposition' => 'attachment; filename="' . $metaData['path'] . '"',
]);
function() use($stream) {
while(ob_get_level() > 0) ob_end_flush();
fpassthru($stream);
},