Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why don't large files download easily in Laravel?

My file (126 MB size, .exe) is giving me issues.

I'm using the standard laravel download method.

I tried increasing the memory but it still either says I have run out of memory, or I download a 0 KB size file.

The documentation doesn't mention anything about large file sizes.

My code is

ini_set("memory_limit","-1"); // Trying to see if this works
return Response::download($full_path);

Anything I am doing wrong?

-- Edit --

Going on Phill Sparks comment, this is what I have and it works. It's a a combinations of Phill's plus some from php.net. Not sure if there is anything in there missing?

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;
}
like image 999
Lango Avatar asked Apr 11 '13 06:04

Lango


People also ask

How can I download large files faster?

For very large size downloads (more than 2GB), we recommend that you use a Download Manager to do the downloading. This can make your download more stable and faster, reducing the risk of a corrupted file. Simply save the download file to your local drive.

How can I download large files without failing?

You could download VM Ware player or any other Virtual Machine software which is available for required OS for free. Android offers . iso file for various version of android which can be downloaded.


2 Answers

This happens because Response::download() loads the file in to memory before serving it to the user. Admittedly this is a flaw in the framework, but most people do not try to serve large files through the framework.

Solution 1 - Put the files you want to download in the public folder, on a static domain, or cdn - bypass Laravel completely.

Understandably, you might be trying to restrict access to your downloads by login, in which case you'll need to craft your own download method, something like this should work...

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;
}

This function is a combination of Response::download() and Laravel's shutdown process. I've not had a chance to test it myself, I don't have Laravel 3 installed at work. Please let me know if it does the job for you.

PS: The only thing this script does not take care of is cookies. Unfortunately the Response::cookies() function is protected. If this becomes a problem you can lift the code from the function and put it in your sendFile method.

PPS: There might be an issue with output buffering; if it is a problem have a look in the PHP manual at readfile() examples, there's a method that should work there.

PPPS: Since you're working with binary files you might want to consider replacing readfile() with fpassthru()

EDIT: Disregard PPS and PPPS, I've updated the code to use fread+print instead as this seems more stable.

like image 194
Phill Sparks Avatar answered Sep 18 '22 08:09

Phill Sparks


You can use the Symfony\Component\HttpFoundation\StreamedResponse like this:

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

for more information check this

like image 23
Alaeddin AL Zeybek Avatar answered Sep 20 '22 08:09

Alaeddin AL Zeybek