Rails#send_file + Nginx X-Accel-Redirect
Sometimes you may need to serve some static files (CSV, PDF, XLS etc) to your users, but only after they have logged in. Obviously you can’t just keep the static file in your public folder as anyone could just use the URL to download files.
One possible solution for protected downloads is to just use the #send_file method provided by Rack to send a non-public file to the user, but serving static files with your app server (Unicorn, Mongrel, Thin etc) is a bad idea as it’s really inefficient. The best approach is to allow the app server to handle the authentication/authorization and then hand the actual downloading to your web server (Nginx, Apache, Lighttpd etc).
In Lighttpd server it can be done by returning X-Sendfile header from your script. Nginx have its own implementation of such idea using X-Accel-Redirect header.
The need for X-Accel-Redirect:
- To deliver large files.
- For those files to not be available to the public
- Would be able to free some resources on server while nginx will handle all slow requests to dynamic content
In this article I will assume that the site is located in /home/kranjith/sites/projects/blog directory and there are some static files (like CSV, PDF, XLS etc) located in /home/kranjith/sites/projects/blog/uploads directory.
First of all, lets take a look at our nginx configuration:
The internal keyword for the /downloads location prevents the uploads folder from being publicly accessible.
Next you need to ensure that Rails knows what server you are using
In config/environments/production.rb file.
In DownloadsController, just do whatever authorization you need to, then use #send_file to serve the file to the user:
I am using CarrierWave to upload files from Rails applications. In config/initializers/carrierwave.rb
And that’s it! With described approach we are able to create very flexible and extremely performance systems for file distribution!
Now I’m going to run through a specific example of downloading a file.
1.Browser makes a request for a file
2.Nginx receives this request. It adds on a header with configuration data that will be required by rails.
3.Nginx passes the request onto Rails and it invokes the relevant controller.
4.The controller makes its authorization checks and calls send_file. Use the absolute path to the file.
5.Rails (Rack to be precise) then decides what to with the file. Rails knows what server we are using (from config/environments/production.rb). Instead of using the file as the body of the request, it will add a header to the response. It uses the X-Accel-Mapping that nginx added earlier to change the file path.
6.Nginx receives this header from rails and interprets it. It finds the location directive and reverses the changes to the path that rails made in step 5.
7.Browser receives the file as if it was a normal download.