为什么在 Laravel 中不能轻松下载大文件?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/15942497/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-14 07:41:57  来源:igfitidea点击:

Why don't large files download easily in Laravel?

phplaravellaravel-3

提问by Lango

My file (126 MB size, .exe) is giving me issues.

我的文件(126 MB 大小,.exe)给我带来了问题。

I'm using the standard laravel download method.

我正在使用标准的 Laravel 下载方法。

I tried increasing the memory but it still either says I have run out of memory, or I download a 0 KB size file.

我尝试增加内存,但它仍然要么说我内存不足,要么下载了一个 0 KB 大小的文件。

The documentation doesn't mention anything about large file sizes.

该文档没有提及有关大文件大小的任何内容。

My code is

我的代码是

ini_set("memory_limit","-1"); // Trying to see if this works
return Response::download($full_path);

Anything I am doing wrong?

我做错了什么?

-- Edit --

- 编辑 -

Going on Phill Sparks comment, this is what I have and it works. It's a a combinations of Phill's plus some from php.net. Not sure if there is anything in there missing?

继续 Phill Sparks 评论,这就是我所拥有的并且它有效。它是 Phill 加上一些来自 php.net 的组合。不知道里面有没有遗漏?

public static function big_download($path, $name = null, array $headers = array())
{
    if (is_null($name)) $name = basename($path);

    // Prepare the headers
    $headers = array_merge(array(
        'Content-Description'       => 'File Transfer',
        'Content-Type'              => File::mime(File::extension($path)),
        'Content-Transfer-Encoding' => 'binary',
        'Expires'                   => 0,
        'Cache-Control'             => 'must-revalidate, post-check=0, pre-check=0',
        'Pragma'                    => 'public',
        'Content-Length'            => File::size($path),
    ), $headers);

    $response = new Response('', 200, $headers);
    $response->header('Content-Disposition', $response->disposition($name));

    // If there's a session we should save it now
    if (Config::get('session.driver') !== '')
    {
        Session::save();
    }

    // Below is from http://uk1.php.net/manual/en/function.fpassthru.php comments
    session_write_close();
    ob_end_clean();
    $response->send_headers();
    if ($file = fopen($path, 'rb')) {
        while(!feof($file) and (connection_status()==0)) {
            print(fread($file, 1024*8));
            flush();
        }
        fclose($file);
    }

    // Finish off, like Laravel would
    Event::fire('laravel.done', array($response));
    $response->foundation->finish();

    exit;
}

回答by Phill Sparks

This happens because Response::download()loads the file in to memory before serving it to the user. Admittedly this is a flaw in the framework, but most people do not try to serve large files through the framework.

发生这种情况是因为Response::download()在将文件提供给用户之前将文件加载到内存中。诚然,这是框架中的一个缺陷,但大多数人不会尝试通过框架提供大文件。

Solution 1 - Put the files you want to download in the public folder, on a static domain, or cdn - bypass Laravel completely.

解决方案 1 - 将您要下载的文件放在公共文件夹、静态域或 cdn 中 - 完全绕过 Laravel。

Understandably, you might be trying to restrict access to your downloads by login, in which case you'll need to craft your own download method, something like this should work...

可以理解的是,您可能试图通过登录来限制对下载的访问,在这种情况下,您需要制定自己的下载方法,这样的方法应该可行...

function sendFile($path, $name = null, array $headers = array())
{
    if (is_null($name)) $name = basename($path);

    // Prepare the headers
    $headers = array_merge(array(
        'Content-Description'       => 'File Transfer',
        'Content-Type'              => File::mime(File::extension($path)),
        'Content-Transfer-Encoding' => 'binary',
        'Expires'                   => 0,
        'Cache-Control'             => 'must-revalidate, post-check=0, pre-check=0',
        'Pragma'                    => 'public',
        'Content-Length'            => File::size($path),
    ), $headers);

    $response = new Response('', 200, $headers);
    $response->header('Content-Disposition', $response->disposition($name));

    // If there's a session we should save it now
    if (Config::get('session.driver') !== '')
    {
        Session::save();
    }

    // Send the headers and the file
    ob_end_clean();
    $response->send_headers();

    if ($fp = fread($path, 'rb')) {
        while(!feof($fp) and (connection_status()==0)) {
            print(fread($fp, 8192));
            flush();
        }
    }

    // Finish off, like Laravel would
    Event::fire('laravel.done', array($response));
    $response->foundation->finish();

    exit;
}

This function is a combination of Response::download()and Laravel's shutdown process. I've not had a chance to test it myself, I don't have Laravel 3 installed at work. Please let me know if it does the job for you.

这个函数是Response::download()和 Laravel 的关闭过程的结合。我没有机会自己测试它,我没有在工作中安装 Laravel 3。请让我知道它是否适合您。

PS:The only thing this script does not take care of is cookies. Unfortunately the Response::cookies()function is protected. If this becomes a problem you can lift the code from the function and put it in your sendFile method.

PS:这个脚本唯一不关心的是cookies。不幸的是Response::cookies()函数是受保护的。如果这成为一个问题,您可以从函数中提取代码并将其放入您的 sendFile 方法中。

PPS:There mightbe an issue with output buffering; if it is a problem have a look in the PHP manual at readfile() examples, there's a method that should work there.

PPS:可能是具有输出缓冲的问题; 如果这是一个问题,请查看readfile() 示例中的 PHP 手册,有一种方法可以在那里工作。

PPPS:Since you're working with binary files you might want to consider replacing readfile()with fpassthru()

PPPS:由于您正在处理二进制文件,因此您可能需要考虑替换readfile()fpassthru()

EDIT:Disregard PPS and PPPS, I've updated the code to use fread+print instead as this seems more stable.

编辑:忽略 PPS 和 PPPS,我已经更新了代码以使用 fread+print,因为这看起来更稳定。

回答by Alaa AL-Zaibak

You can use the Symfony\Component\HttpFoundation\StreamedResponse like this:

你可以像这样使用 Symfony\Component\HttpFoundation\StreamedResponse:

$response = new StreamedResponse(
    function() use ($filePath, $fileName) {
        // Open output stream
        if ($file = fopen($filePath, 'rb')) {
            while(!feof($file) and (connection_status()==0)) {
                print(fread($file, 1024*8));
                flush();
            }
            fclose($file);
        }
    },
    200,
    [
        'Content-Type' => 'application/octet-stream',
        'Content-Disposition' => 'attachment; filename="' . $fileName . '"',
    ]);

return $response;

for more information check this

有关更多信息,请检查

回答by Arda

I'm using the readfile_chunked()custom method as stated in php.net here. For Laravel 3, I've extended the response method like this:

我正在使用readfile_chunked()php.net here 中所述的自定义方法。对于 Laravel 3,我扩展了这样的响应方法:

Add this file as applications/libraries/response.php

将此文件添加为 applications/libraries/response.php

<?php
class Response extends Laravel\Response {

    //http://www.php.net/manual/en/function.readfile.php#54295
    public static function readfile_chunked($filename,$retbytes=true) { 
       $chunksize = 1*(1024*1024); // how many bytes per chunk 
       $buffer = ''; 
       $cnt =0; 
       // $handle = fopen($filename, 'rb'); 
       $handle = fopen($filename, 'rb'); 
       if ($handle === false) { 
           return false; 
       } 
       while (!feof($handle)) { 
           $buffer = fread($handle, $chunksize); 
           echo $buffer; 
           ob_flush(); 
           flush(); 
           if ($retbytes) { 
               $cnt += strlen($buffer); 
           } 
       } 
           $status = fclose($handle); 
       if ($retbytes && $status) { 
           return $cnt; // return num. bytes delivered like readfile() does. 
       } 
       return $status; 

    } 
}

Then comment out this line in application/config/application.php:

然后在 application/config/application.php 中注释掉这一行:

'Response'      => 'Laravel\Response',

Example code:

示例代码:

//return Response::download(Config::get('myconfig.files_folder').$file->upload, $file->title);

header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename='.$file->title);
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . File::size(Config::get('myconfig.files_folder').$file->upload));
ob_clean();
flush();
Response::readfile_chunked(Config::get('myconfig.files_folder').$file->upload);
exit;

Works great so far.

到目前为止效果很好。

回答by Kees Sonnema

You need to increase your memory_limit like so:

您需要像这样增加 memory_limit:

You need to increase memory_limit using the ini_set function e.g ini_set('memory_limit','128M');

您需要使用 ini_set 函数来增加 memory_limit,例如 ini_set('memory_limit','128M');

This will work for you i hope!

我希望这对你有用!

i found the answer here: https://stackoverflow.com/a/12443644/1379394

我在这里找到了答案:https: //stackoverflow.com/a/12443644/1379394

回答by mike rigley

2020 Laravel 7 there is a better way:

2020 Laravel 7 还有更好的办法:

return response()->download($pathToFile);

I've used this with a file 398mbwith no issues, when the same file was causing problems with previous solutions.

当同一个文件导致以前的解决方案出现问题时,我已经将它与一个398mb的文件一起使用,没有任何问题。

From Laravel docs: "The download method may be used to generate a response that forces the user's browser to download the file at the given path. The download method accepts a file name as the second argument to the method, which will determine the file name that is seen by the user downloading the file. Finally, you may pass an array of HTTP headers as the third argument to the method:

来自Laravel 文档“下载方法可用于生成强制用户浏览器在给定路径下载文件的响应。下载方法接受文件名作为该方法的第二个参数,这将确定文件名这是用户下载文件时看到的。最后,您可以将一组 HTTP 标头作为第三个参数传递给该方法:

return response()->download($pathToFile);

return response()->download($pathToFile, $name, $headers);

return response()->download($pathToFile)->deleteFileAfterSend();

We also have streamed downloads which may suit more: Laravel docs

我们也有可能更适合的流式下载:Laravel docs