Protecting Your Downloads

May 15, 2008 | Author: hassox | Publisher: hassox

Sometimes you need to protect files on your server, and allow only certain people to access them. In this tutorial, we'll look at a couple of ways this can be accomplished in Merb.

Protecting downloads can cause issues. Downloads, like uploads can tie up your mongrel / thin / ebb so that your server is effectively locked while downloading.

This can happen in Merb too, but it’s not as easy to do. It’s just like Merb to go and make this stuff easy! You can still lock your mongrels in a file download, but I’ll leave it up to you to work out how because you’ll really have to try ;)

For our example, the only requirement is that the person is logged into the site. If they’re logged in, then they can download it. I’ll do this the long way so it’s clear where the condition is set.

The Easy Way

The send_file, and send_data methods allow a file to be sent to the client from an action. send_data is useful for binary data, but they both work effectively the same.

The easiest way to control a download is just send it in the controller action like this: class MyController < Application def download if logged_in? send_file( "path/to/file" ) else "Sorry You can't download that" end end end

So if the person is logged in, they get the file, otherwise we just render a simple string back to the browser to let them know they can’t have it.

The absolute cool part about this, is that the file is read, and then transmitted to the client outside the mutex lock. It doesn’t hold your sever to ransom just to download the file.

If your using an event driven server, like Ebb or Thin Merb has goodness to help make the most of the fast event loop, but slower actions like uploads and downloads. You can defer the action (and any other slow action) to run outside the event loop all together and get it’s own shiny thread.

Merb::Config[:deferred_actions] = ["/relative/url/to/protected/download"]

Another Way (Save Your Merbs)

If you don’t want your merbs to have to worry about this at all, and your using nginx, you can just pass the downloading on up to the proxy server. Merb has a built in way to let you do this if your using Nginx. The nginx_send_file method instructs Nginx to serve the file directly by setting the X-Accel-Redirect header. This isn’t quite as simple as just calling the method though.

The following headers are not set by Nginx based on the file it’s told to download so you need to manage these yourself.

Content-Type
Content-Disposition
Accept-Ranges
Set-Cookie
Cache-Control
Expires

You also need some Nginx configuration

location /protected/ {
    internal;
    root   /path/to/download/directory;
}

This tells Nginx to watch for a match on the location path, only serve the file if it’s been requested internally (eg via X-Accel-Redirect) and serve it from the given root directory. You also need to set send_file on; in your config.

You can see it’s not as simple just sending the file. Lets take a look at what your controller might look like. class MyController < Application def download if logged_in? headers['Content-Disposition'] = "attachment; filename = #{get_the_file_name}" headers['Content-Type'] ="#{get_the_file_content_type}" nginx_send_file( "/protected" / "path/to/file" ) redirect url(:home) else "Sorry You can't download that" end end end

It’s important here to use the string version for the keys in the headers hash at the moment, and not symbols. It will leave you with headaches if you do.

In the call to nginx_send_file we just pass the relative url of where to find the file. This needs to match with your Nginx configuration, and Nginx will do the rest. If your going to use this method, I’m sure a quick helper could neaten it up a bit but you get the idea.

Using this method you’re delegating the download completely to Nginx, giving your merbs maximum breathing space.

Well I don’t think there’s much more to it really. Protecting your downloads in merb is really very simple.

Comments

Sign in to make your voice heard