【问题标题】:Why don't large files download easily in Laravel?为什么 Laravel 不能轻松下载大文件?
【发布时间】:2013-04-03 06:39:03
【问题描述】:

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

【问题讨论】:

    标签: php laravel laravel-3


    【解决方案1】:

    2020 Laravel 7 有更好的办法:

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

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

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

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

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

    【讨论】:

      【解决方案2】:

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

      更多信息请查看this

      【讨论】:

        【解决方案3】:

        我正在使用 php.net here 中所述的 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;
        

        到目前为止效果很好。

        【讨论】:

          【解决方案4】:

          这是因为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 的 shutdown process 的组合。我自己没有机会测试它,我没有在工作中安装 Laravel 3。请让我知道它是否适合您。

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

          PPS:可能输出缓冲存在问题;如果有问题,请查看readfile() examples 的 PHP 手册,那里有一个应该可以工作的方法。

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

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

          【讨论】:

          • 它几乎成功了,我一直在使用 readfile 和 fpassthru() 获取文件未找到错误。但我查看了链接并将其与您的链接结合起来制作了一些有效的东西。虽然我不确定它有多正确?我编辑了我的问题以显示最新的。
          • 嗨@Lango,我看不出您的解决方案有任何问题。我已经更新了我的答案,包括 fread+print 方法和ob_get_clean()。我不明白为什么 fpassthru 在 fread+print 时不起作用,因为它们使用相同的文件指针 - 但如果它对你有用,那就继续吧!
          【解决方案5】:

          你需要像这样增加你的 memory_limit:

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

          我希望这对你有用!

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

          【讨论】:

          • 没有 ini_set("memory_limit","-1");这样做?
          • 需要是文件的大小吗?
          • no 不一定是文件的大小。 -1 不起作用。
          • 但是.. 它必须比明显的文件大小大
          • 这里的内存指的是ram?如果它是 1 gig 下载和两个人同时下载怎么办。这是否意味着它需要 2 gig 内存?
          猜你喜欢
          • 2016-05-11
          • 1970-01-01
          • 2015-11-15
          • 2016-10-10
          • 1970-01-01
          • 1970-01-01
          • 2015-01-17
          • 1970-01-01
          • 2011-09-13
          相关资源
          最近更新 更多