mirror of
https://github.com/jazzband/django-downloadview.git
synced 2026-03-16 22:40:25 +00:00
Refs #1 - Experimental support for Nginx X-Accel. Work in progress
This commit is contained in:
parent
66c83a8914
commit
1f8876ba4f
7 changed files with 106 additions and 23 deletions
|
|
@ -10,6 +10,8 @@ from django.core.urlresolvers import reverse_lazy as reverse
|
|||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
|
||||
from django_downloadview.nginx import XAccelRedirectResponse
|
||||
|
||||
from demoproject.download.models import Document
|
||||
|
||||
|
||||
|
|
@ -98,3 +100,24 @@ class ObjectDownloadViewTestCase(DownloadTestCase):
|
|||
'attachment; filename=hello-world.txt')
|
||||
self.assertEqual(open(self.files['hello-world.txt']).read(),
|
||||
response.content)
|
||||
|
||||
|
||||
class XAccelRedirectDecoratorTestCase(DownloadTestCase):
|
||||
@temporary_media_root()
|
||||
def test_response(self):
|
||||
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)
|
||||
self.assertTrue(isinstance(response, XAccelRedirectResponse))
|
||||
self.assertEquals(response['Content-Type'],
|
||||
'text/plain; charset=utf-8')
|
||||
self.assertFalse('ContentEncoding' in response)
|
||||
self.assertEquals(response['Content-Disposition'],
|
||||
'attachment; filename=hello-world.txt')
|
||||
self.assertEquals(response['X-Accel-Redirect'],
|
||||
'/download-optimized/document/hello-world.txt')
|
||||
|
|
|
|||
|
|
@ -7,4 +7,6 @@ urlpatterns = patterns('demoproject.download.views',
|
|||
name='download_hello_world'),
|
||||
url(r'^document/(?P<slug>[a-zA-Z0-9_-]+)/$', 'download_document',
|
||||
name='download_document'),
|
||||
url(r'^document-nginx/(?P<slug>[a-zA-Z0-9_-]+)/$',
|
||||
'download_document_nginx', name='download_document_nginx'),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
from os.path import abspath, dirname, join
|
||||
|
||||
from django_downloadview import DownloadView, ObjectDownloadView
|
||||
from django_downloadview.nginx import x_accel_redirect
|
||||
|
||||
from demoproject.download.models import Document
|
||||
|
||||
|
|
@ -14,3 +15,7 @@ download_hello_world = DownloadView.as_view(filename=hello_world_file,
|
|||
storage=None)
|
||||
|
||||
download_document = ObjectDownloadView.as_view(model=Document)
|
||||
|
||||
download_document_nginx = x_accel_redirect(download_document,
|
||||
media_root='/var/www/files',
|
||||
media_url='/download-optimized')
|
||||
|
|
|
|||
|
|
@ -6,10 +6,12 @@ See `Nginx X-accel documentation <http://wiki.nginx.org/X-accel>`_.
|
|||
from datetime import datetime, timedelta
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.http import HttpResponse
|
||||
|
||||
from django_downloadview.middlewares import BaseDownloadMiddleware
|
||||
from django_downloadview.decorators import DownloadDecorator
|
||||
from django_downloadview.middlewares import BaseDownloadMiddleware
|
||||
from django_downloadview.utils import content_type_to_charset
|
||||
|
||||
|
||||
#: Default value for X-Accel-Buffering header.
|
||||
|
|
@ -24,19 +26,15 @@ 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,
|
||||
def __init__(self, redirect_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]
|
||||
basename = basename or redirect_url.split('/')[-1]
|
||||
self['Content-Disposition'] = 'attachment; filename=%s' % basename
|
||||
self['X-Accel-Redirect'] = url
|
||||
self['X-Accel-Redirect'] = 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'
|
||||
|
|
@ -52,18 +50,25 @@ class XAccelRedirectResponse(HttpResponse):
|
|||
|
||||
class BaseXAccelRedirectMiddleware(BaseDownloadMiddleware):
|
||||
"""Looks like a middleware, but configurable."""
|
||||
def __init__(self, expires=None, with_buffering=None, limit_rate=None):
|
||||
def __init__(self, media_root, media_url, expires=None,
|
||||
with_buffering=None, limit_rate=None):
|
||||
"""Constructor."""
|
||||
self.media_root = media_root
|
||||
self.media_url = media_url
|
||||
self.expires = expires
|
||||
self.with_buffering = with_buffering
|
||||
self.limit_rate = limit_rate
|
||||
|
||||
def file_to_url(response):
|
||||
return response.filename
|
||||
def get_redirect_url(self, response):
|
||||
"""Return redirect URL for file wrapped into response."""
|
||||
absolute_filename = response.filename
|
||||
relative_filename = absolute_filename[len(self.media_root):]
|
||||
return '/'.join((self.media_url.rstrip('/'),
|
||||
relative_filename.strip('/')))
|
||||
|
||||
def process_download_response(self, request, response):
|
||||
"""Replace DownloadResponse instances by NginxDownloadResponse ones."""
|
||||
url = self.file_to_url(response)
|
||||
redirect_url = self.get_redirect_url(response)
|
||||
if self.expires:
|
||||
expires = self.expires
|
||||
else:
|
||||
|
|
@ -71,8 +76,8 @@ class BaseXAccelRedirectMiddleware(BaseDownloadMiddleware):
|
|||
expires = response.expires
|
||||
except AttributeError:
|
||||
expires = None
|
||||
return XAccelRedirectResponse(url=url,
|
||||
content_type=response.content_type,
|
||||
return XAccelRedirectResponse(redirect_url=redirect_url,
|
||||
content_type=response['Content-Type'],
|
||||
basename=response.basename,
|
||||
expires=expires,
|
||||
with_buffering=self.with_buffering,
|
||||
|
|
@ -87,8 +92,22 @@ class XAccelRedirectMiddleware():
|
|||
"""
|
||||
def __init__(self):
|
||||
"""Use Django settings as configuration."""
|
||||
try:
|
||||
media_root = settings.NGINX_DOWNLOAD_MIDDLEWARE_MEDIA_ROOT
|
||||
except AttributeError:
|
||||
raise ImproperlyConfigured(
|
||||
'settings.NGINX_DOWNLOAD_MIDDLEWARE_MEDIA_ROOT is required by '
|
||||
'%s middleware' % self.__class__.name)
|
||||
try:
|
||||
media_url = settings.NGINX_DOWNLOAD_MIDDLEWARE_MEDIA_URL
|
||||
except AttributeError:
|
||||
raise ImproperlyConfigured(
|
||||
'settings.NGINX_DOWNLOAD_MIDDLEWARE_MEDIA_URL is required by '
|
||||
'%s middleware' % self.__class__.name)
|
||||
super(XAccelRedirectMiddleware, self).__init__(
|
||||
expires=settings.NGINX_DOWNLOAD_MIDDLEWARE_EXPIRESS,
|
||||
media_root,
|
||||
media_url,
|
||||
expires=settings.NGINX_DOWNLOAD_MIDDLEWARE_EXPIRES,
|
||||
with_buffering=settings.NGINX_DOWNLOAD_MIDDLEWARE_WITH_BUFFERING,
|
||||
limit_rate=settings.NGINX_DOWNLOAD_MIDDLEWARE_LIMIT_RATE)
|
||||
|
||||
|
|
|
|||
18
django_downloadview/utils.py
Normal file
18
django_downloadview/utils.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
"""Utility functions."""
|
||||
import re
|
||||
|
||||
|
||||
charset_pattern = re.compile(r'charset=(?P<charset>.+)$', re.I | re.U)
|
||||
|
||||
|
||||
def content_type_to_charset(content_type):
|
||||
"""Return charset part of content-type header.
|
||||
|
||||
>>> from django_downloadview.utils import content_type_to_charset
|
||||
>>> content_type_to_charset('text/html; charset=utf-8')
|
||||
'utf-8'
|
||||
|
||||
"""
|
||||
match = re.search(charset_pattern, content_type)
|
||||
if match:
|
||||
return match.group('charset')
|
||||
|
|
@ -17,6 +17,14 @@ django_downloadview Package
|
|||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
:mod:`file` Module
|
||||
------------------
|
||||
|
||||
.. automodule:: django_downloadview.file
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
:mod:`middlewares` Module
|
||||
-------------------------
|
||||
|
||||
|
|
@ -41,6 +49,14 @@ django_downloadview Package
|
|||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
:mod:`utils` Module
|
||||
-------------------
|
||||
|
||||
.. automodule:: django_downloadview.utils
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
:mod:`views` Module
|
||||
-------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -19,14 +19,10 @@ 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/`.
|
||||
* Document storage root is :file:`/var/www/files/`
|
||||
* FileField's ``upload_to`` is "document".
|
||||
|
||||
Configure Document storage:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
storage = FileSystemStorage(location='var/www/files',
|
||||
url='/optimized-download')
|
||||
Files live in ``/var/www/files/document/`` folder.
|
||||
|
||||
As is, Django is to serve the files, i.e. load chunks into memory and stream
|
||||
them.
|
||||
|
|
@ -82,6 +78,8 @@ Optionally customize configuration (default is "use Nginx's defaults").
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
NGINX_DOWNLOAD_MIDDLEWARE_MEDIA_ROOT = MEDIA_ROOT
|
||||
NGINX_DOWNLOAD_MIDDLEWARE_MEDIA_URL = '/optimized-download'
|
||||
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.
|
||||
|
|
@ -104,7 +102,9 @@ In some urls.py:
|
|||
from django_downloadview.nginx import x_accel_redirect
|
||||
|
||||
|
||||
download = x_accel_redirect(ObjectDownloadView.as_view(model=Document))
|
||||
download = x_accel_redirect(ObjectDownloadView.as_view(model=Document),
|
||||
media_root=settings.MEDIA_ROOT,
|
||||
media_url='/optimized-download')
|
||||
|
||||
# ... URL patterns using ``download``
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue