The goal:
1. Let people download files really really fast
2. Protect those files so they can only accessed if a user logs in
The fastest way to serve files to the user is by letting the webserver take care of it directly. Classically, you’d have to make the folders or files you want to serve public to the webserver to accomplish this. But the problem with this is that then anybody who knows the filename can get access.
The solution:
Both Lighttpd and Apache (with mod_xsendfile) support a special HTTP header called X-Sendfile that will serve the private file of your choosing directly to the user. Speed and security together. Sweet!
You make up this header in your Rails app, put the local absolute path to the file in, and send it as a response.
Edge Rails (2.x): You can use the send_file helper method with the :x_sendfile => true option. E.g.
send_file '/path/to.png', :x_sendfile => true, :type => 'image/png'
X-Sendfile Example with Apache2
This is easy once you know how, although it took me a while to get everything right. This is on a server I have complete control over – there might be other issues on a virtual host, because you probably can’t use XSendFileAllowAbove as it would be a security risk.
Apache config file http.conf
XSendFile on XSendFileAllowAbove on
Then in your controller
filename = "/var/www/myfile.xyz"
response.headers['Content-Type'] = "application/force-download"
response.headers['Content-Disposition'] = "attachment; filename=\"#{File.basename(filename)}\""
response.headers["X-Sendfile"] = filename
response.headers['Content-length'] = File.size(filename)
render :nothing => true
note: Internet explorer stalled on “getting file information”, and could never complete downloads until I added the following:
response.headers['Cache-Control'] = 'must-revalidate'
X-Sendfile Example with Lighttpd
config file (Lighttpd):
$HTTP["host"] == "myhost.berkeley.edu" {
server.document-root = var.app + "/public"
url.rewrite = ( "^/$" => "index.html", "^([^.]+)$" => "$1.html" )
server.error-handler-404 = "/dispatch.fcgi"
fastcgi.server = ( ".fcgi" =>
( "localhost" =>
( "min-procs" => 10,
"max-procs" => 10,
"allow-x-send-file" => "enable",
"socket" => var.app + "/tmp/sockets/fcgi.socket",
"bin-path" => var.app + "/public/dispatch.fcgi",
"bin-environment" => ( "RAILS_ENV" => "development" )
)
)
)
}
The plugin
There is a plugin that simplifies usage of the X-Sendfile header. It can be used in a manner similar to Rails’ send_file command.
x_send_file('/path/to/file.zip')
The goal:
1. Let people download files really really fast
2. Protect those files so they can only accessed if a user logs in
The fastest way to serve files to the user is by letting the webserver take care of it directly. Classically, you’d have to make the folders or files you want to serve public to the webserver to accomplish this. But the problem with this is that then anybody who knows the filename can get access.
The solution:
Both Lighttpd and Apache (with mod_xsendfile) support a special HTTP header called X-Sendfile that will serve the private file of your choosing directly to the user. Speed and security together. Sweet!
You make up this header in your Rails app, put the local absolute path to the file in, and send it as a response.
Edge Rails (2.x): You can use the send_file helper method with the :x_sendfile => true option. E.g.
send_file '/path/to.png', :x_sendfile => true, :type => 'image/png'
X-Sendfile Example with Apache2
This is easy once you know how, although it took me a while to get everything right. This is on a server I have complete control over – there might be other issues on a virtual host, because you probably can’t use XSendFileAllowAbove as it would be a security risk.
Apache config file http.conf
XSendFile on XSendFileAllowAbove on
Then in your controller
filename = "/var/www/myfile.xyz"
response.headers['Content-Type'] = "application/force-download"
response.headers['Content-Disposition'] = "attachment; filename=\"#{File.basename(filename)}\""
response.headers["X-Sendfile"] = filename
response.headers['Content-length'] = File.size(filename)
render :nothing => true
note: Internet explorer stalled on “getting file information”, and could never complete downloads until I added the following:
response.headers['Cache-Control'] = 'must-revalidate'
X-Sendfile Example with Lighttpd
config file (Lighttpd):
$HTTP["host"] == "myhost.berkeley.edu" {
server.document-root = var.app + "/public"
url.rewrite = ( "^/$" => "index.html", "^([^.]+)$" => "$1.html" )
server.error-handler-404 = "/dispatch.fcgi"
fastcgi.server = ( ".fcgi" =>
( "localhost" =>
( "min-procs" => 10,
"max-procs" => 10,
"allow-x-send-file" => "enable",
"socket" => var.app + "/tmp/sockets/fcgi.socket",
"bin-path" => var.app + "/public/dispatch.fcgi",
"bin-environment" => ( "RAILS_ENV" => "development" )
)
)
)
}
The plugin
There is a plugin that simplifies usage of the X-Sendfile header. It can be used in a manner similar to Rails’ send_file command.
x_send_file('/path/to/file.zip')