29
votes

I'm keeping all uploads on a custom, external drive. The files are being stored via custom API.

In Laravel 5.2, I can do this for a local file to download it:

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

Unfortunately, when I pass a URL instead of a path, Laravel throws an error:

The file "https://my-cdn.com/files/image.jpg" does not exist

(the URL is a dummy of course).

Is there any way I can download the image.jpg file using Laravel's implementation or do I do this with plain PHP instead?

6
@Jamesking56 Nah, first of all the post you linked is about S3 which Laravel supports out of the box as a possible remote disk (so not my case). Secondly, even if the post was similar, there's basically no answer.lesssugar

6 Answers

45
votes

There's no magic, you should download external image using copy() function, then send it to user in the response:

$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);
28
votes

TL;DR
Use the streamDownload response if you're using 5.6 or greater. Otherwise implement the function below.

Original Answer
Very similar to the "download" response, Laravel has a "stream" response which can be used to do this. Looking at the API, both of the these functions are wrappers around Symfony's BinaryFileResponse and StreamedResponse classes. In the Symfony docs they have good examples of how to create a StreamedResponse

Below is my implementation using 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;
});

Update 2018-01-17

This has now been merged into Laravel 5.6 and has been added to the 5.6 docs. The streamDownload response can be called like this:

<?php

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

As for 2020 this is all pretty easy.

2 lines of code to download to your server and 2 lines to upload to browser (if needed).

Assume you want an HTML for google home page to get saved locally onto your server, and then return an HTTP response to initiate a browser to download the file at client slide:

// 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 savde 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, $name, $headers);

Look up Laravel Storage facade documentation for details on disk configuration and put method and Response with file download documentaion for returning file with an HTTP reponse.

6
votes

Why not just use a simple redirect?

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

try this script:

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

If you do not want end user can see main_url in header try this:

$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
votes

You could pull down the content of the original file, work out its mime type and then craft your own response giving the right headers.

I do this myself using a PDF library, but you could modify to use file_get_contents() to pull down the remote assets:

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')
        );

You need to change $pdf to be the data of the file, Content-Type to contain the mimetype of the file that the data is and Content-Disposition is the filename you want it to appear as.

Not sure whether this will work, mine simply makes the browser popup with a PDF download so I'm not sure whether it'll work for embedding CDN files... Worth a try though.