views:

28

answers:

2

Hi All,

About Application

I am working on an e-commerce application in PHP. To keep URL's secure, product download links are kept behind PHP. There is a file, say download.php, which accepts few parameter via GET and verifies them against a database. If all goes well, it serves file using readfile() function in PHP.

About Problem

Now problem comes when file to be passed to readfile() is larger than memory limit set in php.ini As this application will be used by many users on shared-hosting we cannot relay on altering php.ini settings.

In our effort to find workarounds, I first thought we can go for fread() calls in while loop but it seems that will impose problems as well as highlighted here http://stackoverflow.com/questions/597159/sending-large-files-reliably-in-php

So my best option is to detect/check if server supports X-Accel-Redirect (in case of Nginx) / X-Sendfile (in case of Apache)

If server supports X-Accel-Redirect / X-Sendfile, I can use them and in else block I can make system admin aware about memory limit enforced by php.ini

Ideally, I want to use server side support like X-Accel-Redirect / X-Sendfile wherever possible, and if that doesn't work - I would like to have a fallback code to read files without readfile().

I am not yet sure as how readfile() and fread() in while loop are different but seems while loop will create problem, again, as suggested in http://stackoverflow.com/questions/597159/sending-large-files-reliably-in-php

Hope to get some help, suggestions, codes, guidance.

Thanks for reading.

-Rahul

A: 

readfile does not take up a large amount of memory. It opens the file, reads a small portion, writes that portion to the browser and then reuses the memory for the next read. It's the same as using fread+echo in a while loop. You will not be constrained by memory-limits, but you will be limited by max_execution_time and such.

If you want to use X-Accel-Redirect support (or similar) provided by your web server, send a header like this (for Nginx):

header('X-Accel-Redirect: /path/to/file');

Your application cannot know if the server supports this. You will need to provide a configuration option so the admin/installer of your software can provide such information manually.

Emil Vikström
@Emil - Thanks for insights into readfile. If readfile() works in small memory, why it threw "out of memory" error when we opened large files. Also same error disappeared as soon as we changed php.ini settings. Also readfile() was my last line so I am sure after readfile, my application isn't processing data further.
rahul286
One more - if I write `header('X-Accel-Redirect: /path/to/file');` and underlaying server is not configured properly, what will happen? Will the header() call go ignored? Will I get some error message?
rahul286
rahul286, someone explains possible memory problems in the comments on http://php.net/readfile as this: "it's because you have output buffering on. Just turn off output buffering immediately before the call to Readfile(). Use something like ob_end_flush()."
Emil Vikström
Also, if your web server doesn't support X-Accel-Redirect (or similar) the header will be sent to the web browser, which will ignore it. You won't get any error message.
Emil Vikström
Emil, I noticed output_buffering issues. But as my codes are packages as wordpress plugin, and I haven't initiated output buffering, I choose to go ahead with my while loop with flush() calls inside loop. It worked. :-)
rahul286
A: 

To detect if the mod_xsendfile apache module installed, you can try this code:

if function_exists('apache_get_modules') 
      && in_array('mod_xsendfile', apache_get_modules()) { 
  header("X-Sendfile"); 
}

But this code just check if the module installed only, that can cause errors if it's installed but configured wrongly

another possible way to do this to setup server-wide variable through Apache's .htaccess:

<IfModule mod_xsendfile.c>
  <Files *.php>
    XSendFile On
    XSendFileAllowAbove On
    SetEnv MOD_X_SENDFILE_ENABLED 1
  </Files>
</IfModule>

and check it form php code:

if ($_SERVER['MOD_X_SENDFILE_ENABLED']) {
  Header(...)
}

The common idea is the same for nginx - just pass the value of status variable to backend via HTTP-header or CGI/FastCGI variable.

Vadim
@Vadim - Thanks for help. Will above code work in both cases - when php is used as Apache'e mod_php OR fastcgi/fcgi?
rahul286
Also, I guess on Nginx, support for X-Accel-Redirect is part of core, so all I need is to detect if (webserver == "nginx") somehow! Right?
rahul286
By the way above code snippet helped a lot. So thanks for it. :-)
rahul286
The first one code snippet works for mod_php only, but the second one works for both mod_php and CGI/FastCGI mode.
Vadim
About nginx - looks like you're right, but your cgi/fastcgi tier don't know anything about the fornt-end server software unlike you'll inform it (using CGI variable or HTTP-header)
Vadim