Laravel: Downloading external files using response()->stream() randomly leads to corrupted downloads

Viewed 2788

I have files stored on a CDN, which are available for download. The files can be up to 100 MB, so I'm using simple chunking method (below).

The download works, but some of the files (mostly PDF-s) end up being corrupted and cannot be opened.

Can anyone point out a flaw or a gotcha in the code?

One thing I can think of is that the headers are not sent in time somehow. Another one is that some of the files are not saved in UTF-8 and this might cause problems reading the file fully. I'm stuck.

Download logic (using stream()):

$url = 'http://example.com/files/example.pdf';

$headers = array(
    'Pragma' => 'public',
    'Expires' => '0',
    'Content-Transfer-Encoding' => 'binary',
    'Cache-Control' => 'must-revalidate, post-check=0, pre-check=0',
    'Content-Type' => 'application/octet-stream',
    'Content-Disposition' => 'attachment; filename="example.pdf"'
);

return response()->stream(function() use ($url) {
    $this->readfileChunked($url);
},200, $headers);

The chunking:

/**
 * Serves the given file in chunks.
 * Source: http://cn2.php.net/manual/en/function.readfile.php#52598
 *
 * @param      $filename
 * @param bool $retbytes
 *
 * @return bool|int
 */
public function readfileChunked($filename, $retbytes = true) {
    $chunkSize = 1024 * 1024; // Size (in bytes) of tiles chunk
    $cnt = 0;
    $handle = fopen($filename, 'rb');

    if ($handle === false) {
        return false;
    }

    while (!feof($handle)) {
        $buffer = fread($handle, $chunkSize);
        echo $buffer;

        ob_flush();
        flush();

        if ($retbytes) {
            $cnt += strlen($buffer);
        }
    }

    $status = fclose($handle);

    if ($retbytes && $status) {
        return $cnt; // return number of bytes delivered like readfile() does
    }

    return $status;
}
1 Answers
Related