2

Is there a way to send a large (about >700mb) file to the Browser without exceeding memory in PHP?

I tried using fpassthru and readfile and it exceedet the memory limit.

Sapena
  • 23
  • 1
  • 4
  • Possible duplicates: http://stackoverflow.com/questions/3626009/getting-allowed-memory-size-of-33554432-bytes-exhausted and http://stackoverflow.com/questions/3674197/fatal-error-allowed-memory-size-of-134217728-bytes-exhausted-tried-to-allocate – Abel Mar 07 '11 at 16:36
  • @Abel I wouldn't consider them duplicates - those have different reasons for eating up all the memory. – Dave Vogt Mar 07 '11 at 16:50
  • What version of PHP are you using (and on what platform)? Also, why do you believe that it's running out of memory? (If a specific error is being generate, please post a code sample and the precise error.) – John Parker Mar 07 '11 at 17:07
  • @Dave Vogt: ok, thanks for pointing that out. I'll leave them in as they're at least similar: a file is send through an internet browser to the server. – Abel Mar 07 '11 at 23:04

4 Answers4

9

The most efficient solution would be to use the X-Sendfile header, if your webserver supports it.

It means you don't need to occupy PHP at all with serving the file, you just send the header and let the web server handle it.

Example (from the Apache mod_xsendfile page:)

header("X-Sendfile: $path_to_somefile");
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; filename=\"$somefile\"");
exit;
Long Ears
  • 4,676
  • 19
  • 15
6

Good old fopen() + fread() + fclose():

<?php
$handle = fopen('/tmp/foo', 'rb');
while (!feof($handle)) {
    echo fread($handle, 8192);
}
fclose($handle);

8192 is the buffer size shown in PHP documentation but in my experience it's better to raise it because you can get an interesting performance boost at the price of very little memory usage increase.

Álvaro González
  • 128,942
  • 37
  • 233
  • 325
3

It sounds like the problems you're having using fpassthru are due to the whole file being be loaded into memory. What you should instead do is read the file data in chunks using the traditional fopen/fread/fclose cycle, outputting the data as you go.

For example:

<?php
    $fileRes = fopen('/path/to/your/file.data', 'rb');
    if(is_resource($fileRes) {
        while (!feof($fileRes)) {
            echo fread($fileRes);
        }
        fclose($fileRes);
    }
    else die("Couldn't open file...");
?>
John Parker
  • 52,372
  • 11
  • 124
  • 125
  • 2
    `fpassthru` and `readfile` do *not* read the whole file into memory unless there's enough space for it. [Have a look at the source](http://svn.php.net/viewvc/php/php-src/branches/PHP_5_3/main/streams/streams.c?revision=307922&view=markup#l1215), this code is pretty much equivalent to what PHP does anyway, except it's less efficient. – Long Ears Mar 07 '11 at 17:04
  • @Long Thought it seemed a bit odd. That said, the OP might be using some older version of PHP, although even then I'd be surprised if such an issue existed. – John Parker Mar 07 '11 at 17:07
  • For reference, I can serve a 700MB file via `readfile` with a 8MB memory_limit. – Long Ears Mar 07 '11 at 17:08
  • 1
    A comment on the fpassthru page you linked to suggests there was a memory leak in PHP 4 so that may well be it. – Long Ears Mar 07 '11 at 17:10
  • @Long I've asked the OP for more info + sample code to see if we can find out what the real issue is. – John Parker Mar 07 '11 at 17:10
  • 4
    Make sure you're not using output buffering - usually, when we see readfile() consuming memory, it's because the user is using output buffering, causing PHP to buffer the readfile(). – TML Mar 08 '11 at 01:16
  • Well, yes the `while fread` method works, but is it normal that it hangs during the download of the file? – Sapena Mar 08 '11 at 08:27
1

fpassthru (as readfile) sends data directly, without much memory allocation. So problem in caching and ob_end_flush will help:

<?php

$fp = fopen($filename, 'rb');

header('Content-Type: ' . mime_type($filename));
header("Content-Length: " . filesize($filename));

ob_end_flush()

fpassthru($fp);
exit;
Liss
  • 11
  • 1