If you serve Django behind Nginx, then you can delegate the file download service to Nginx and get increased performance:
See Nginx X-accel documentation [1] for details.
Let’s start in the situation described in the demo application:
We are to make it more efficient with Nginx.
Note
Examples below are taken from the demo project.
Use django_downloadview.nginx.assert_x_accel_redirect() function as a shortcut in your tests.
demo/demoproject/nginx/tests.py:
"""Test suite for demoproject.nginx."""
from django.core.files import File
from django.core.urlresolvers import reverse_lazy as reverse
from django_downloadview.nginx import assert_x_accel_redirect
from django_downloadview.test import temporary_media_root
from demoproject.download.models import Document
from demoproject.download.tests import DownloadTestCase
class XAccelRedirectDecoratorTestCase(DownloadTestCase):
@temporary_media_root()
def test_response(self):
"""'download_document_nginx' view returns a valid X-Accel response."""
document = Document.objects.create(
slug='hello-world',
file=File(open(self.files['hello-world.txt'])),
)
download_url = reverse('download_document_nginx',
kwargs={'slug': 'hello-world'})
response = self.client.get(download_url)
self.assertEquals(response.status_code, 200)
# Validation shortcut: assert_x_accel_redirect.
assert_x_accel_redirect(
self,
response,
content_type="text/plain; charset=utf-8",
charset="utf-8",
basename="hello-world.txt",
redirect_url="/download-optimized/document/hello-world.txt",
expires=None,
with_buffering=None,
limit_rate=None)
# Check some more items, because this test is part of
# django-downloadview tests.
self.assertFalse('ContentEncoding' in response)
self.assertEquals(response['Content-Disposition'],
'attachment; filename=hello-world.txt')
class InlineXAccelRedirectTestCase(DownloadTestCase):
@temporary_media_root()
def test_response(self):
"""X-Accel optimization respects ``attachment`` attribute."""
document = Document.objects.create(
slug='hello-world',
file=File(open(self.files['hello-world.txt'])),
)
download_url = reverse('download_document_nginx_inline',
kwargs={'slug': 'hello-world'})
response = self.client.get(download_url)
assert_x_accel_redirect(
self,
response,
content_type="text/plain; charset=utf-8",
charset="utf-8",
attachment=False,
redirect_url="/download-optimized/document/hello-world.txt",
expires=None,
with_buffering=None,
limit_rate=None)
Right now, this test should fail, since you haven’t implemented the view yet.
At the end of this setup, the test should pass, but you still have to setup Nginx!
You have two options: global setup with a middleware, or per-view setup with decorators.
If you want to delegate all file downloads to Nginx, then use django_downloadview.nginx.XAccelRedirectMiddleware.
Register it in your settings:
MIDDLEWARE_CLASSES = (
# ...
'django_downloadview.nginx.XAccelRedirectMiddleware',
# ...
)
Setup the middleware:
NGINX_DOWNLOAD_MIDDLEWARE_MEDIA_ROOT = MEDIA_ROOT # Could be elsewhere.
NGINX_DOWNLOAD_MIDDLEWARE_MEDIA_URL = '/proxied-download'
Optionally fine-tune the middleware. Default values are None, which means “use Nginx’s defaults”.
NGINX_DOWNLOAD_MIDDLEWARE_EXPIRES = False # Force no expiration.
NGINX_DOWNLOAD_MIDDLEWARE_WITH_BUFFERING = False # Force buffering off.
NGINX_DOWNLOAD_MIDDLEWARE_LIMIT_RATE = False # Force limit rate off.
If you want to delegate file downloads to Nginx on a per-view basis, then use django_downloadview.nginx.x_accel_redirect() decorator.
demo/demoproject/nginx/views.py:
"""Views."""
from django.conf import settings
from django_downloadview.nginx import x_accel_redirect
from demoproject.download import views
download_document_nginx = x_accel_redirect(
views.download_document,
source_dir='/var/www/files',
destination_url='/download-optimized')
download_document_nginx_inline = x_accel_redirect(
views.download_document_inline,
source_dir=settings.MEDIA_ROOT,
destination_url='/download-optimized')
And use it in som URL conf, as an example in demo/demoproject/nginx/urls.py:
"""URL mapping."""
from django.conf.urls import patterns, url
urlpatterns = patterns(
'demoproject.nginx.views',
url(r'^document-nginx/(?P<slug>[a-zA-Z0-9_-]+)/$',
'download_document_nginx', name='download_document_nginx'),
url(r'^document-nginx-inline/(?P<slug>[a-zA-Z0-9_-]+)/$',
'download_document_nginx_inline',
name='download_document_nginx_inline'),
)
Note
In real life, you’d certainly want to replace the “download_document” view instead of registering a new view.
See Nginx X-accel documentation [1] for details.
Here is what you could have in /etc/nginx/sites-available/default:
charset utf-8;
# Django-powered service.
upstream frontend {
server 127.0.0.1:8000 fail_timeout=0;
}
server {
listen 80 default;
# File-download proxy.
#
# Will serve /var/www/files/myfile.tar.gz when passed URI
# like /optimized-download/myfile.tar.gz
#
# See http://wiki.nginx.org/X-accel
# and https://github.com/benoitbryon/django-downloadview
location /proxied-download {
internal;
# Location to files on disk.
# See Django's settings.NGINX_DOWNLOAD_MIDDLEWARE_MEDIA_ROOT
alias /var/www/files/;
}
# Proxy to Django-powered frontend.
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://frontend;
}
}
... where specific configuration is the location /optimized-download section.
Note
/proxied-download is not available for the client, i.e. users won’t be able to download files via /optimized-download/<filename>.
Warning
Make sure Nginx can read the files to download! Check permissions.
Add charset utf-8; in your nginx configuration file.
Check your settings.NGINX_DOWNLOAD_MIDDLEWARE_MEDIA_ROOT in Django configuration VS alias in nginx configuration: in a standard configuration, they should be equal.
References
[1] | (1, 2) http://wiki.nginx.org/X-accel |