8

我的文件(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;
}
4

5 回答 5

15

发生这种情况是因为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:输出缓冲可能存在问题;如果这是一个问题,请查看readfile() 示例的 PHP 手册,有一种方法应该在那里工作。

PPPS:由于您使用的是二进制文件,您可能需要考虑用readfile()fpassthru ()替换

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

于 2013-04-11T09:47:32.260 回答
4

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

有关更多信息,请查看

于 2016-04-28T09:31:48.010 回答
3

我正在使用此处readfile_chunked()php.net 中所述的自定义方法。对于 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;

到目前为止效果很好。

于 2013-07-24T14:20:41.247 回答
1

2020 Laravel 7 有更好的方法:

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

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

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

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

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

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

我们还提供了可能更适合的流式下载:Laravel 文档

于 2020-05-27T18:53:40.813 回答
0

您需要像这样增加 memory_limit :

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

我希望这对你有用!

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

于 2013-04-11T07:01:38.843 回答