Search code examples
djangonginxx-accel-redirect

Serving protected files with Django and Nginx X-accel-redirect


I'm trying to get Nginx and Django to play together to serve downloadable protected files. I just cannot get it to work. Here's my Nginx config:

location ~ ^.*/protected-test/ {
alias /<path-to-my-protected-files-on-server>/;
internal;
}

the relevant urls.py for viewing the file(s):

url(r'^static_files/downloads/protected-test/(?P<filename>.+)$', 'download_or_view',
{'download_dir': '%s%s' % (settings.MEDIA_ROOT, 'downloads/protected-test/'),
'content_disposition_type': 'inline',
'protected': 'True'},
name='protected_files')

my view:

def download_or_view(request, content_disposition_type, download_dir, filename=None, protected=False):

'''Allow a file to be downloaded or viewed,based on the request type and
     content disposition value.'''

if request.method == 'POST':
    full_path = '%s%s' % (download_dir, request.POST['filename'])
    short_filename = str(request.POST['filename'])
else:
    full_path = '%s%s' % (download_dir, filename)
    short_filename = str(filename)

serverfile = open(full_path, 'rb')
contenttype, encoding = mimetypes.guess_type(short_filename)
response = HttpResponse(serverfile, mimetype=contenttype)

if protected:
    url = _convert_file_to_url(full_path)
    response['X-Accel-Redirect'] = url.encode('utf-8')

response['Content-Disposition'] = '%s; filename="%s"' % (content_disposition_type, smart_str(short_filename))
response['Content-Length'] = os.stat(full_path).st_size

return response

I have 2 values in my settings file:

NGINX_ROOT = (os.path.join(MEDIA_ROOT, 'downloads/protected-test'))
NGINX_URL = '/protected-test'

_convert_file_to_url() takes the full file path and, using the two settings values above, turns it into a url that (I thought) Nginx would allow:

<domain-name>/protected-test/<filename>

So, if I try to access:

<domain-name>/static_files/downloads/protected-test/<filename>

In my browser window, it doesn't allow it (404). Good.

BUT - if I try to access that url from a form download, which I want to allow, I get a redirect in the browser to:

<domain-name>/protected-test/<filename>

and it's a 404 as well.

I've tried so many different configurations my brain now hurts. :-)

Should I not be reading the file with open(), and let Nginx serve it? If I remove that line, it returns a file with the dreaded zero bytes. Why do I still get a 404 on the redirected url??


Solution

  • Should I not be reading the file with open(),

    That's correct. Your script shouldn't be opening the file. You just tell Nginx where the file exists and let it open the file and serve it.

    I believe you want to just return an empty response after setting the appropriate headers

    return HttpResponse('', mimetype=contenttype)
    

    In PHP I setup the Nginx accel redirect by doing:

    //Set content type and caching headers
    //...
    header("X-Accel-Redirect: ".$filenameToProxy);
    exit(0);
    

    i.e. exiting immediately after setting the header.

    For the continuing 404 problem, you've probably got an error in the Nginx conf, but you need to post the rest to be sure. Your external URL appears to be something like:

    static_files/downloads/protected-test/(?P<filename>.+)$
    

    This will be matched on:

    location ~ ^.*/protected-test/ {
        alias /<path-to-my-protected-files-on-server>/;
        internal;
    }
    

    giving the 404.

    There is no need (and it's quite confusing) to have the same word protected-test in both the external URL and internal URL. I'd recommend not doing that i.e. have the external URL be like:

    /static_files/downloads/(?P<filename>.+)$
    

    Then have the internal location block be:

    location ~ ^/protected-test {
        alias /<path-to-my-protected-files-on-server>;
        internal;
    }
    

    And then when you setup the x-accel-redirect header, swap between the two:

    external_path = "/static_files/downloads";
    nginx_path = "/protected-test";
    filenameToProxy = str_replace(external_path, nginx_path, full_path);
    header("X-Accel-Redirect: ".$filenameToProxy);
    

    Rather than having the word protected-test be on both sides of the request.