Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/php/283.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Php 从Laravel存储下载,无需在内存中加载整个文件_Php_Laravel - Fatal编程技术网

Php 从Laravel存储下载,无需在内存中加载整个文件

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, [

我正在使用Laravel存储,我想为用户提供一些(大于内存限制的)文件。我的代码的灵感来自于一个in-SO,如下所示:

$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);
},