How to return stream response in Laravel (for large files)

Updated: January 16, 2024 By: Guest Contributor Post a comment

Introduction

Handling large files effectively is crucial to providing a good user experience and maintaining server performance. In Laravel, one of the best ways to handle the download of large files is by streaming responses rather than directly returning file contents. This helps to reduce memory usage drastically. In this tutorial, we’ll explore how to stream large files to the user, ensuring your Laravel application remains performant and scalable.

Prerequisites

Before we dive into streaming responses, here’s what you need to get started:

  • A running Laravel installation (version 5.5 or higher recommended)
  • Basic knowledge of Laravel’s request/response structure
  • Familiarity with PHP’s file handling functions

Basic Streaming Response

To return a stream response in Laravel, you’ll use the response()->stream() method. Here’s a simple example of a controller method that streams a large text file to the browser:

<?php

namespace App\Http\Controllers;

use Illuminate\Support\Facades\Storage;

class FileDownloadController extends Controller
{
    public function streamLargeFile()
    {
        $filePath = storage_path('app/largefile.txt');

        return response()->stream(
            function() use ($filePath) {
                $stream = fopen($filePath, 'r');
                fpassthru($stream);
                fclose($stream);
            },
            200,
            [
                'Content-Type' => 'text/plain',
                'Content-Disposition' => 'attachment; filename="largefile.txt"'
            ]
        );
    }
}

The above code defines a controller method that opens a read stream to a file, writes its content to the output buffer, and then sends the file to the client’s browser as a downloadable attachment.

Custom Headers and Streamed Downloads

At times, you may want to have more control over the HTTP headers in your stream response. Here’s how you can implement streaming with custom headers:

<?php

// ... (previous code)

public function customStreamHeaders()
{
    // ...

    return response()->stream(
        // Streaming logic remains the same
        // ...
        function () {
            // Your streaming logic here
        },
        200,
        [
            'Content-Type' => $customMimeType,
            'Cache-Control' => 'no-cache, must-revalidate', // Custom cache control
            'Content-Disposition' => 'inline; filename="' . $customFileName . '"',
        ]
    );
}

This example introduces custom HTTP headers, including specifying the MIME type and setting cache control to prevent caching of the response.

Note: Be sure to validate and sanitize custom data used for header values to avoid security issues such as header injection attacks.

Chunked Reading for Extremely Large Files

For extremely large files, reading a file in chunks can prevent PHP memory buffer overflow. To achieve this, you would use a loop coupled with a read buffer:

<?php

// ... (previous code)

public function chunkedFileStream()
{
    // ...

    return response()->stream(
        function() use ($filePath) {
            $stream = fopen($filePath, 'r');
            while (!feof($stream)) {
                echo fread($stream, 1024 * 1024); // Reading in chunks of 1MB
                flush();
                ob_flush();
            }
            fclose($stream);
        },
        200,
        [
            'Content-Type' => 'text/plain' // Custom headers can be added here
        ]
    );
}

This code reads the file in 1MB chunks until the end of the file is reached, sending each chunk immediately to the user, which is more efficient for very large files.

Using Laravel’s Filesystem for Streaming

Laravel’s filesystem provides a fluent, abstracted way to work with local and remote storage options. Here is how you can use Laravel’s Filesystem to stream a response:

<?php

// ... (previous code)

use Illuminate\Support\Facades\Storage;

public function streamWithFilesystem()
{
    // ...

    $path = 'path/to/your/largefile.ext';
    $filesystem = Storage::disk('local');

    if (!$filesystem->exists($path)) {
        abort(404);
    }

    $stream = $filesystem->readStream($path);

    return response()->stream(
        function() use ($stream) {
            fpassthru($stream);
        },
        200,
        [
            'Content-Type' => $filesystem->mimeType($path),
            'Content-Disposition' => 'attachment; filename="' . basename($path) . '"'
        ]
    );
}

In this example, we use Laravel’s Storage facade to access the file system and return a stream. This approach allows for abstraction and supports local as well as cloud storages like Amazon S3, making it a versatile and scalable solution for Laravel applications.

Conclusion

Streaming large files in Laravel can greatly reduce memory usage and help maintain server performance. The approaches we’ve covered range from basic streaming with built-in PHP functions to leveraging Laravel’s powerful filesystem abstraction layer. By understanding and utilizing these techniques, you can handle large file downloads gracefully in your Laravel applications.