Last Updated: November 28, 2016
·
4.814K
· jasny

X-SendFile in Apache2

Normally when want to let a user download a file, you simply stick it in a dir under the document root and let Apache do the rest.

Let PHP serve the download

However in some cases that is not good enough. You might need to do some authenticate first or you need to lookup the actual file name. In that case you would use PHP, which would result in a script looking like this:

authenticate(); # authenticate and authorize, redirect/exit if failed
$file = determine_file();

if (!file_exists($file)) trigger_error("File '$file' doesn't exist.", E_USER_ERROR);

header("Content-type: application/octet-stream");
header('Content-Disposition: attachment; filename="' . basename($file) . '"');
header("Content-Length: ". filesize($file));
readfile($file);

This means PHP has to read in the file, which goes through the output buffer, is flushed to Apache and processed before send to client. If I want to make the caching based on last-modified work, you need to check the if-modified-since request header, check the mtime of the file and send a 304 result header.

X-SendFile to the rescue

Wouldn’t it be nicer to tell Apache, please send that file, and be done with it. Well, you can. When you enable ‘mod_xsendfile’ in Apache, you can send an X-SendFile header, which is processed by Apache.

authenticate(); # authenticate and authorize, redirect/exit if failed
$file = determine_file();

header("X-Sendfile: $file");
header("Content-type: application/octet-stream");
header('Content-Disposition: attachment; filename="' . basename($file) . '"');

Note that this technique was copied from Lighttp. NGinx supports an alternative header called X-Accel-Redirect.