21

I have a Laravel 5.4 app where authenticated users need to be able to download private files from S3 storage. I've setup a route and controller to allow private file downloads.

The code looks like this:

Route:

Route::get('file/{filename}', 'FileController@download')->where(['filename' => '[A-Za-z0-9-._\/]+'])->name('file')->middleware('auth:employee');

Controller:

public function download($fileName)
{
    if (!$fileName || !Storage::exists($fileName)) {
        abort(404);
    }

    return response()->stream(function() use ($fileName) {
        $stream = Storage::readStream($fileName);
        fpassthru($stream);
        if (is_resource($stream)) {
            fclose($stream);
        }
    }, 200, [
        'Cache-Control'         => 'must-revalidate, post-check=0, pre-check=0',
        'Content-Type'          => Storage::mimeType($fileName),
        'Content-Length'        => Storage::size($fileName),
        'Content-Disposition'   => 'attachment; filename="' . basename($fileName) . '"',
        'Pragma'                => 'public',
    ]);
}

All working fine, but when I had a closer look to the Laravel docs, I found that they just talk about response()->download().

If I implement that kind of response, my code would look like this:

public function download($fileName)
{
    if (!$fileName || !Storage::exists($fileName)) {
        abort(404);
    }

    $file = Storage::get($fileName);

    return response()->download($file, $fileName, [
        'Content-Type'  => Storage::mimeType($fileName),
    ]);
}

Both functions can be found in the API docs.

My question: what would be the preferred way to go and what are the advantages/disadvantages of each?

From what I've gathered so far:

Stream:

  • Does not require the whole file to be loaded into memory
  • Suitable for large files

Download:

  • Requires less code
Jones03
  • 1,207
  • 2
  • 12
  • 30

1 Answers1

22

When you call the Laravel response() helper, it returns an instance of the Illuminate\Routing\ResponseFactory. The ResponseFactory has these two methods: download and stream - the two methods in question. If you dig a little bit deeper, you'll see that download returns an instance of \Symfony\Component\HttpFoundation\BinaryFileResponse, while stream returns a \Symfony\Component\HttpFoundation\StreamedResponse - these are both Symfony components.

Digging through the code here isn't necessary, but it is nice to have an understanding of what's going on under the hood. Now that we know the underlying objects returned are from the Symfony HTTP Component, we can consult the Symfony docs and see what they recommend using. Typically, streams are used when the size of the file is unknown, such as when you are generating the file on the fly. In most other cases, the BinaryFileResponse generated by the download method will be sufficient for your needs.

You can take a look at a much more in-depth explanation of HTTP Streaming and its use cases here.

samrap
  • 5,595
  • 5
  • 31
  • 56