Basics
------
Normally when we want users to download a file, that file is put in a folder
under the web application root and the web server does the rest.
Most of the time this is not enough, because the user needs to be authenticated
or the file name should be searched in the database.
In that case a script like this would be used:
~~~
[php]
//.. authenticate and authorize, redirect/exit if failed
authenticate();
//.. get the file to be downloaded, redirect/exit if it does not exist
$file = determine_file();
//.. get the content of the requested file
$content=file_get_contents($file);
//.. send appropriate headers
header("Content-type: application/octet-stream");
header('Content-Disposition: attachment; filename="' . basename($file) .
'"');
header("Content-Length: ". filesize($file));
echo $content;
~~~
For this in Yii we have
[CHttpRequest->sendFile()](http://www.yiiframework.com/doc/api/1.1/CHttpRequest#sendFile-detail
"CHttpRequest->sendFile()").
Is something wrong with this?
-----------------------------
This means that our script has to read the file from the disk, which goes
through the output buffer, is flushed to the web server and processed before
sent to the client.
In case of big files it will consume a lot of memory, and if the file is bigger
than memory_limit, it could break the script or exceed script max execution
time.
When loading entire files into memory, you then have to unload them, and your
thread is busy for that process.
Caching is a real pain too.
Not to mention implementing download resuming.
Ideal solution
--------------
Ideally our script would process user authentication and/or search the database
for the file name and then instruct the web server to send the file to the user.
This way the script would be more responsive as it could continue processing as
soon as it instructs the web server to process the request.
Meet the X-Sendfile
-------------------
X-Sendfile is a feature that allows a web application to redirect the request
for a file to the web server that in turn processes the request, this way
eliminating the need to perform tasks like reading the file and sending it to
the user.
X-Sendfile can improve your web application performance, especially when working
with very big files, as the web server will load the file you specified and send
it to the user.
But what is X-Sendfile?
-----------------------
X-Sendfile is a special header option that tells the web server to ignore the
content of the response and replace it by the file that is specified in the
X-Sendfile header.
When the web server encounters the presence of such header it will discard all
output and send the file specified by that header using web server internals
including all optimizations like caching-headers and download resuming.
Is there something I should know before using X-Sendfile?
---------------------------------------------------------
This option is disabled by default as it's still not a standard feature.
If this option is disabled by the web server, when this method is called a
download configuration dialog will open but the downloaded file will have 0
bytes.
This option allows to download files that are not under web folders, and even
files that are otherwise protected (deny from all) like .htaccess.
Different web servers has implemented this option using different directive:
<pre>
sendfile directive - web server application using it
--------------------- -------------------------------
X-Sendfile - Apache, Lighttpd v1.5, Cherokee
X-LIGHTTPD-send-file - Lighttpd v1.4
X-Accel-Redirect - Nginx, Cherokee
</pre>
The disadvantage to using X-SendFile is that you lose control over the transfer
mechanism. What if you want to perform some tasks after the client has received
the file? For example: to allow a user to download a file only once. With
X-Sendfile this could not be done because the script continues to run as soon as
it sends the file to the web server for processing and so the script could not
know if the download was successfull.
How to use this from Yii?
-------------------------------------
From Yii version 1.1.6 there is
[CHttpRequest->xSendFile()](http://www.yiiframework.com/doc/api/1.1/CHttpRequest#xSendFile-detail
"CHttpRequest->xSendFile()") that allows to send files using the
X-Sendfile directive.
~~~
[php]
CHttpRequest->xSendFile($fullName, $options)
~~~
Where `$fullName` is the file name with full path and `$options` are the
additional options like `saveName`, `mimeType`, `xHeader` and `terminate`.
`saveName`: file name shown to the user. Useful when creating temporary files
with cryptic names, to avoid collisions, but still serving the user a nicely
named file.
`mimeType`: mime type of the file for proper file handling, if not set it will
be guessed automatically based on the file name.
`xHeader`: by default this method uses the directive X-Sendfile, this option can
be used to set different directive if needed by the web server.
`terminate`: by default the script will terminate execution after sending the
X-Sendfile header, setting this option to `false` this can be prevented.
Example
-------
~~~
[php]
Yii::app()->request->xSendFile('/home/user/Pictures/picture1.jpg',array(
'saveName'=>'RequestedFile.jpg',
'mimeType'=>'image/jpeg',
'terminate'=>false,
));
~~~
The image located on the server at '/home/user/Pictures/picture1.jpg' will be
downloaded as 'RequestedFile.jpg'. The users downloading the file does not know
where the requested file is located on the server.
Additional documentations
-------------------------
[Apache](https://tn123.org/mod_xsendfile/ "Apache")
[Lighttpd](http://redmine.lighttpd.net/wiki/lighttpd/X-LIGHTTPD-send-file[Lighttpd](http://redmine.lighttpd.net/projects/lighttpd/wiki/X-LIGHTTPD-send-file
"Lighttpd")
[Nginx](http://wiki.nginx.org/XSendfile "Nginx")
[Cherokee](http://www.cherokee-project.com/doc/other_goodies.html#x-sendfile
"Cherokee")