php 为什么Laravel中的大文件下载不容易?

t98cgbkg  于 12个月前  发布在  PHP
关注(0)|答案(6)|浏览(118)

我的文件(126 MB大小,.exe)给我的问题。
我使用的是标准的Laravel下载方法。
我尝试增加内存,但它仍然说我已经用完内存,或者我下载了一个0 KB大小的文件。
文档没有提到任何关于大文件大小的内容。
我的代码

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

字符串

我做错了什么吗

--编辑--
继续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;
}

aydmsdu9

aydmsdu91#

这是因为Response::download()在将文件提供给用户之前将其加载到内存中。诚然,这是框架中的一个缺陷,但大多数人不会尝试通过框架提供大文件。
解决方案1 -把你想下载的文件放在公共文件夹中,在一个静态域,或cdn -完全绕过Laravel。
可以理解的是,你可能会试图通过登录来限制对下载的访问,在这种情况下,你需要制定自己的下载方法,像这样的东西应该可以工作。

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;
}

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

**PS:**这个脚本唯一没有处理的就是cookies。不幸的是Response::cookies()函数是受保护的。如果这成为一个问题,你可以从函数中提取代码并将其放入sendFile方法中。
**PPS:**输出缓冲可能有问题;如果有问题,请查看PHP手册中的readfile()示例,其中有一个方法应该可以工作。

  • PPPS:* 由于您正在处理二进制文件,因此可能需要考虑将readfile()替换为fpassthru()
    **编辑:**忽略PPS和PPPS,我已经更新了代码,使用fread+print代替,因为这似乎更稳定。
nwlls2ji

nwlls2ji2#

您可以像这样使用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;

字符串
有关更多信息,请检查此

iq3niunx

iq3niunx3#

我在这里使用了php.net中所述的readfile_chunked()自定义方法。对于Laravel 3,我扩展了响应方法如下:
将此文件添加为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; 

    } 
}

字符串
然后在application/config/application.php中注解掉这一行:

'Response'      => 'Laravel\\Response',


范例程式码:

//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;


目前为止效果很好

ep6jt1vc

ep6jt1vc4#

2020 Laravel 7有一个更好的方法:

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

字符串
我已经用这个文件398mb没有问题,当同一个文件导致问题与以前的解决方案。
Laravel docs:* “download方法可以用来生成一个响应,强制用户的浏览器在给定的路径下下载文件。download方法接受一个文件名作为方法的第二个参数,它将确定用户下载文件时看到的文件名。最后,你可以传递一个HTTP头数组作为方法的第三个参数:*

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

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

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


我们也有流下载,这可能更适合,特别是如果服务器内存不足是一个问题:Laravel文档

ep6jt1vc

ep6jt1vc5#

你需要像这样增加你的memory_limit:
您需要使用ini_set函数增加memory_limit,例如ini_set('memory_limit','128M');
这将为你工作,我希望!
我在这里找到了答案:https://stackoverflow.com/a/12443644/1379394

jutyujz0

jutyujz06#

我在laravel 10中遇到了同样的问题。我使用了这段代码,它对我来说工作得很好。只需在控制器中创建一个路由,并尝试通过调用URL下载并使用此代码。

use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;

        header("Pragma: public");
        header("Expires: 0");
        header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
        header("Cache-Control: public");
        header("Content-Description: File Transfer");
        header("Content-Disposition: attachment; filename=\"" . $file->file_name . "\"");
        header('Content-Type: text/csv');
        header("Content-Transfer-Encoding: binary");
        header("Content-Length: " . File::size(Storage::path($file->file_path)));
        header("X-Pad: avoid browser bug");
        header("Accept-Ranges: bytes");
        $file = fopen(Storage::path($file->file_path), "rb");
        if ($file) {
            while (!feof($file)) {
                print(fread($file, 1024 * 8));
                flush();
                if (connection_status() != 0) {
                    fclose($file);
                    die();
                }
            }
            fclose($file);
        }

字符串

相关问题