【问题标题】:Download a file in Laravel using a URL to external resource使用指向外部资源的 URL 在 Laravel 中下载文件
【发布时间】:2016-12-12 00:15:46
【问题描述】:

我将所有上传内容保存在自定义的外部驱动器上。这些文件是通过自定义 API 存储的。

在 Laravel 5.2 中,我可以对本地文件执行此操作以下载它:

return response()->download('path/to/file/image.jpg');

不幸的是,当我传递 URL 而不是路径时,Laravel 会抛出错误:

文件“https://my-cdn.com/files/image.jpg”不存在

(当然,URL 是一个虚拟的)。

有什么方法可以使用 Laravel 的实现来下载 image.jpg 文件,或者我可以用普通的 PHP 来代替吗?

【问题讨论】:

  • @Jamesking56 不,首先你链接的帖子是关于 S3 的,Laravel 开箱即用地支持它作为可能的远程磁盘(所以不是我的情况)。其次,即使帖子类似,也基本上没有答案。

标签: php laravel


【解决方案1】:

至于 2020 年,这一切都很容易。

2 行代码下载到您的服务器,2 行代码上传到浏览器(如果需要)。

假设您希望将 Google 主页的 HTML 保存到本地服务器上,然后返回 HTTP 响应以启动浏览器以在客户端幻灯片上下载文件:

// Load the file contents into a variable.
$contents = file_get_contents('www.google.com');

// Save the variable as `google.html` file onto
// your local drive, most probably at `your_laravel_project/storage/app/` 
// path (as per default Laravel storage config)
Storage::disk('local')->put('google.html', $contents);

// -- Here your have saved the file from the URL 
// -- to your local Laravel storage folder on your server.
// -- By default this is `your-laravel-project/storage/app` folder.

// Now, if desired, and if you are doing this within a web application's
// HTTP request (as opposite to CLI application)
// the file can be sent to the browser (client) with the response
// that instructs the browser to download the file at client side:

// Get the file path within you local filesystem
$path = Storage::url('google.html');

// Return HTTP response to a client that initiates the file downolad
return response()->download($path);

查看 Laravel Storage facade 文档以获取有关磁盘配置和 put 方法的详细信息以及 Response with file download 文档以返回带有 HTTP 响应的文件。

【讨论】:

    【解决方案2】:

    TL;DR
    如果您使用的是 5.6 或更高版本,请使用 streamDownload response。否则执行下面的函数。

    原答案
    与“下载”响应非常相似,Laravel 有一个“流”响应可用于执行此操作。查看API,这两个函数都是 Symfony 的 BinaryFileResponse 和 StreamedResponse 类的包装器。在 Symfony 文档中,他们有很好的 examples of how to create a StreamedResponse

    下面是我使用 Laravel 的实现:

    <?php
    
    use Illuminate\Support\Str;
    use Symfony\Component\HttpFoundation\ResponseHeaderBag;
    
    Route::get('/', function () {
        $response = response()->stream(function () {
            echo file_get_contents('http://google.co.uk');
        });
    
        $name = 'index.html';
    
        $disposition = $response->headers->makeDisposition(
            ResponseHeaderBag::DISPOSITION_ATTACHMENT,
            $name,
            str_replace('%', '', Str::ascii($name))
        );
    
        $response->headers->set('Content-Disposition', $disposition);
    
        return $response;
    });
    

    2018 年 1 月 17 日更新

    这现在是merged into Laravel 5.6 并已添加到5.6 docs。 streamDownload 响应可以这样调用:

    <?php
    
    Route::get('/', function () {
        return response()->streamDownload(function () {
            echo file_get_contents('https://my.remote.com/file/store-12345.jpg');
        }, 'nice-name.jpg');
    });
    

    【讨论】:

    • 您的方法比copy() 好得多,尤其是当它是像视频下载这样的大文件时。
    • 这会导致 php 内存不足。从错误来看,它似乎正试图将整个文件加载到我做echo file_get_contents...的内存中
    • @JonMcClung 是的,你是对的。在上面的示例中,“echo file_get_contents”将整个文件拉入内存。我这样做只是为了使示例简单。我认为如果你想真正流式传输文件,你必须向它传递一个像这样的流上下文github.com/twistor/flysystem-http/blob/… 这是流上下文的文档php.net/manual/en/function.stream-context-create.php
    • 你能用readfile代替file_get_contents吗? this question 的答案似乎表明 readfile 返回一个流,它在流式响应时可能表现更好?
    • @Dwight 是的,我刚刚使用了readfile($url);,它可以工作。我觉得就像Header("Content-disposition: attachment; filename=$name"); Header("Content-Type: application/download"); readfile($url);
    【解决方案3】:

    试试这个脚本:

    // $main_url is path(url) to your remote file
    $main_url = "http://dl.aviny.com/voice/marsieh/moharram/92/shab-02/mirdamad/mirdamad-m92-sh2-01.mp3";
    header("Content-disposition:attachment; filename=$main_url");
    readfile($main_url);
    

    如果您不希望最终用户可以在标头中看到 main_url,请尝试以下操作:

    $main_url = "http://dl.aviny.com/voice/marsieh/moharram/92/shab-02/mirdamad/mirdamad-m92-sh2-01.mp3";
    $file = basename($main_url);
    header("Content-disposition:attachment; filename=$file");
    readfile($main_url);
    

    【讨论】:

      【解决方案4】:

      没有什么神奇的,你应该使用copy()函数下载外部图像,然后在响应中发送给用户:

      $filename = 'temp-image.jpg';
      $tempImage = tempnam(sys_get_temp_dir(), $filename);
      copy('https://my-cdn.com/files/image.jpg', $tempImage);
      
      return response()->download($tempImage, $filename);
      

      【讨论】:

      • 复制的文件是被垃圾回收删除了还是应该自己取消链接?如果是这样,我们如何在此处返回响应时执行此操作?
      • 我现在看到了,Laravel 在某个时候在下载响应中添加了一个-&gt;deleteFileAfterSend() 方法。
      • 我需要做什么?如果我在我的服务器位置添加文件以永久保存?
      【解决方案5】:

      您可以提取原始文件的内容,计算出它的 MIME 类型,然后制作您自己的响应并给出正确的标题。

      我自己使用 PDF 库执行此操作,但您可以修改为使用 file_get_contents() 拉取远程资源:

      return Response::make(
              $pdf,
              200,
              array(
                  'Content-Description' => 'File Transfer',
                  'Cache-Control' => 'public, must-revalidate, max-age=0, no-transform',
                  'Pragma' => 'public',
                  'Expires' => 'Sat, 26 Jul 1997 05:00:00 GMT',
                  'Last-Modified' => ''.gmdate('D, d M Y H:i:s').' GMT',
                  'Content-Type' => 'application/pdf', false,
                  'Content-Disposition' => ' attachment; filename="chart.pdf";',
                  'Content-Transfer-Encoding' => ' binary',
                  'Content-Length' => ' '.strlen($pdf),
                  'Access-Control-Allow-Origin' => $origin,
                  'Access-Control-Allow-Methods' =>'GET, PUT, POST, DELETE, HEAD, PATCH',
                  'Access-Control-Allow-Headers' =>'accept, origin, content-type',
                  'Access-Control-Allow-Credentials' => 'true')
              );
      

      您需要将$pdf 更改为文件的数据,将Content-Type 更改为包含数据所在文件的mimetype,Content-Disposition 是您希望它显示为的文件名。

      不确定这是否可行,我只是让浏览器弹出一个 PDF 下载,所以我不确定它是否适用于嵌入 CDN 文件...不过值得一试。

      【讨论】:

      • 问题是关于下载远程 URL。不是本地文件。
      • 其实这个问题是模棱两可的。文本描述假定文件下载到服务器。代码 sn-p 建议需要将带有 HTTP 响应的文件上传到浏览器(这反过来又假设文件存在于服务器的文件系统上,因此它已经从 URL 下载)。 @Jamesking56 关于第二部分是正确的。为什么要投反对票。
      • @Artistan 请注意我的回答中的这一行“但您可以修改为使用 file_get_contents() 来下拉远程资产”
      【解决方案6】:

      为什么不只使用简单的重定向?

      return \Redirect::to('https://my-cdn.com/files/image.jpg');
      

      【讨论】:

      • 这将重定向到外部资源 - 并停留在页面上,这绝对不是我需要的 ;)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-02-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多