Refs #1 - Introduced experimental support for Nginx X-Accel. WORK IN PROGRESS.

This commit is contained in:
Benoit Bryon 2012-11-19 14:41:36 +01:00
parent 46542cdc3c
commit 41e00d312d
4 changed files with 228 additions and 0 deletions

View file

@ -0,0 +1,101 @@
"""Let Nginx serve files for increased performance.
See `Nginx X-accel documentation <http://wiki.nginx.org/X-accel>`_.
"""
from datetime import datetime, timedelta
from django.conf import settings
from django.http import HttpResponse
from django_downloadview.middlewares import BaseDownloadMiddleware
from django_downloadview.decorators import DownloadDecorator
#: Default value for X-Accel-Buffering header.
DEFAULT_BUFFERING = None
if not hasattr(settings, 'NGINX_DOWNLOAD_MIDDLEWARE_BUFFERING'):
setattr(settings, 'NGINX_DOWNLOAD_MIDDLEWARE_BUFFERING', DEFAULT_BUFFERING)
#: Default value for X-Accel-Limit-Rate header.
DEFAULT_LIMIT_RATE = None
if not hasattr(settings, 'NGINX_DOWNLOAD_MIDDLEWARE_LIMIT_RATE'):
setattr(settings, 'NGINX_DOWNLOAD_MIDDLEWARE_LIMIT', DEFAULT_LIMIT_RATE)
def content_type_to_charset(content_type):
return 'utf-8'
class XAccelRedirectResponse(HttpResponse):
"""Http response that delegate serving file to Nginx."""
def __init__(self, url, content_type, basename=None, expires=None,
with_buffering=None, limit_rate=None):
"""Return a HttpResponse with headers for Nginx X-Accel-Redirect."""
super(XAccelRedirectResponse, self).__init__(content_type=content_type)
basename = basename or url.split('/')[-1]
self['Content-Disposition'] = 'attachment; filename=%s' % basename
self['X-Accel-Redirect'] = url
self['X-Accel-Charset'] = content_type_to_charset(content_type)
if with_buffering is not None:
self['X-Accel-Buffering'] = with_buffering and 'yes' or 'no'
if expires:
expire_seconds = timedelta(expires - datetime.now()).seconds
self['X-Accel-Expires'] = expire_seconds
elif expires is not None: # We explicitely want it off.
self['X-Accel-Expires'] = 'off'
if limit_rate is not None:
self['X-Accel-Limit-Rate'] = limit_rate and '%d' % limit_rate \
or 'off'
class BaseXAccelRedirectMiddleware(BaseDownloadMiddleware):
"""Looks like a middleware, but configurable."""
def __init__(self, expires=None, with_buffering=None, limit_rate=None):
"""Constructor."""
self.expires = expires
self.with_buffering = with_buffering
self.limit_rate = limit_rate
def file_to_url(response):
return response.filename
def process_download_response(self, request, response):
"""Replace DownloadResponse instances by NginxDownloadResponse ones."""
url = self.file_to_url(response)
if self.expires:
expires = self.expires
else:
try:
expires = response.expires
except AttributeError:
expires = None
return XAccelRedirectResponse(url=url,
content_type=response.content_type,
basename=response.basename,
expires=expires,
with_buffering=self.with_buffering,
limit_rate=self.limit_rate)
class XAccelRedirectMiddleware():
"""Apply X-Accel-Redirect globally.
XAccelRedirectResponseHandler with django settings.
"""
def __init__(self):
"""Use Django settings as configuration."""
super(XAccelRedirectMiddleware, self).__init__(
expires=settings.NGINX_DOWNLOAD_MIDDLEWARE_EXPIRESS,
with_buffering=settings.NGINX_DOWNLOAD_MIDDLEWARE_WITH_BUFFERING,
limit_rate=settings.NGINX_DOWNLOAD_MIDDLEWARE_LIMIT_RATE)
#: Apply BaseXAccelRedirectMiddleware to ``view_func`` response.
#:
#: Proxies additional arguments (``*args``, ``**kwargs``) to
#: :py:meth:`django_downloadview.nginx.BaseXAccelRedirectMiddleware.__init__`:
#: ``expires``, ``with_buffering``, and ``limit_rate``.
x_accel_redirect = DownloadDecorator(BaseXAccelRedirectMiddleware)

View file

@ -25,6 +25,14 @@ django_downloadview Package
:undoc-members:
:show-inheritance:
:mod:`nginx` Module
-------------------
.. automodule:: django_downloadview.nginx
:members:
:undoc-members:
:show-inheritance:
:mod:`response` Module
----------------------

View file

@ -13,6 +13,7 @@ Contents
.. toctree::
:maxdepth: 2
nginx
api/modules

118
docs/nginx.txt Normal file
View file

@ -0,0 +1,118 @@
###################
Nginx optimisations
###################
If you serve Django behind Nginx, then you can delegate the file download
service to Nginx and get increased performance:
* lower resources used by Python/Django workers ;
* faster download.
See `Nginx X-accel documentation`_ for details.
****************************
Configure some download view
****************************
As an example, let's consider the following download view:
* mapped on ``/document/<object-slug>/download``
* returns DownloadResponse corresponding to Document's model FileField
* Document storage root is :file:`/var/www/files/`.
Configure Document storage:
.. code-block:: python
storage = FileSystemStorage(location='var/www/files',
url='/optimized-download')
As is, Django is to serve the files, i.e. load chunks into memory and stream
them.
Nginx is much more efficient for the actual streaming.
***************
Configure Nginx
***************
See `Nginx X-accel documentation`_ for details.
In this documentation, let's suppose we have something like this:
.. code-block:: nginx
# Will serve /var/www/files/myfile.tar.gz
# When passed URI /protected_files/myfile.tar.gz
location /optimized-download {
internal;
alias /var/www/files;
}
.. note::
``/optimized-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.
************************************************
Global delegation, with XAccelRedirectMiddleware
************************************************
If you want to delegate all file downloads to Nginx, then use
:py:class:`django_downloadview.nginx.XAccelRedirectMiddleware`.
Register it in your settings:
.. code-block:: python
MIDDLEWARE_CLASSES = (
# ...
'django_downloadview.nginx.XAccelRedirectMiddleware',
# ...
)
Optionally customize configuration (default is "use Nginx's defaults").
.. code-block:: python
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.
*************************************************
Local delegation, with x_accel_redirect decorator
*************************************************
If you want to delegate file downloads to Nginx on a per-view basis, then use
:py:func:`django_downloadview.nginx.x_accel_redirect` decorator.
In some urls.py:
.. code-block:: python
# ... import Document and django.core.urls
from django_downloadview import ObjectDownloadView
from django_downloadview.nginx import x_accel_redirect
download = x_accel_redirect(ObjectDownloadView.as_view(model=Document))
# ... URL patterns using ``download``
**********
References
**********
.. target-notes::
.. _`Nginx X-accel documentation`: http://wiki.nginx.org/X-accel