Commit c7abf477 authored by Diederik van der Boor's avatar Diederik van der Boor
Browse files

Support streaming responses from the storage class

This is needed to fetch content from S3 without needing a full_path
parent eecfc006
import mimetypes
import os
from django.core.files.storage import Storage, File
from django.utils.functional import cached_property
......@@ -11,7 +11,7 @@ class PrivateFile(object):
def __init__(self, request, storage, relative_name):
self.request = request
self.storage = storage
self.storage = storage # type: Storage
self.relative_name = relative_name
@cached_property
......@@ -19,13 +19,38 @@ class PrivateFile(object):
# Not using self.storage.open() as the X-Sendfile needs a normal path.
return self.storage.path(self.relative_name)
def open(self, mode='rb'):
"""
Open the file for reading.
:rtype: django.core.files.storage.File
"""
file = self.storage.open(self.relative_name, mode=mode) # type: File
return file
def exists(self):
return os.path.exists(self.full_path)
"""
Check whether the file exists.
"""
return self.storage.exists(self.relative_name)
@cached_property
def content_type(self):
"""
Return the HTTP ``Content-Type`` header value for a filename.
"""
mimetype, encoding = mimetypes.guess_type(self.full_path)
mimetype, encoding = mimetypes.guess_type(self.relative_name)
return mimetype or 'application/octet-stream'
@cached_property
def size(self):
"""
Return the size of the file in bytes.
"""
return self.storage.size(self.relative_name)
@cached_property
def modified_time(self):
"""
Return the last-modified time
"""
return self.storage.get_modified_time(self.relative_name)
......@@ -5,7 +5,8 @@ import os
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.http import HttpResponse
from django.http import FileResponse, HttpResponse
from django.utils.http import http_date
from django.utils.lru_cache import lru_cache
from django.utils.module_loading import import_string
from django.views.static import serve
......@@ -15,6 +16,8 @@ from django.views.static import serve
def get_server_class(path):
if '.' in path:
return import_string(path)
elif path == 'streaming':
return DjangoStreamingServer
elif path == 'django':
return DjangoServer
elif path == 'apache':
......@@ -27,7 +30,22 @@ def get_server_class(path):
)
class DjangoServer(object):
class DjangoStreamingServer(object):
"""
Serve static files as streaming chunks in Django.
"""
@staticmethod
def serve(private_file):
# FileResponse will submit the file in 8KB chunks
response = FileResponse(private_file.open())
response['Content-Type'] = private_file.content_type
response['Content-Length'] = private_file.size
response["Last-Modified"] = http_date(private_file.modified_time.timestamp())
return response
class DjangoServer(DjangoStreamingServer):
"""
Serve static files from the local filesystem through Django.
This is a bad idea for most situations other than testing.
......@@ -38,7 +56,14 @@ class DjangoServer(object):
@staticmethod
def serve(private_file):
# This supports If-Modified-Since and sends the file in 8KB chunks
return serve(private_file.request, private_file.full_path, document_root='/', show_indexes=False)
try:
full_path = private_file.full_path
except NotImplementedError:
# S3 files, fall back to streaming server
return DjangoStreamingServer.serve(private_file)
else:
# Using Django's serve gives If-Modified-Since support out of the box.
return serve(private_file.request, full_path, document_root='/', show_indexes=False)
class ApacheXSendfileServer(object):
......
......@@ -85,7 +85,7 @@ class PrivateStorageDetailView(SingleObjectMixin, PrivateStorageView):
def get_path(self):
file = getattr(self.object, 'file')
return file.path
return file.name
def can_access_file(self, private_file):
"""
......
......@@ -40,7 +40,7 @@ setup(
install_requires=[],
requires=[
'Django (>=1.7)',
'Django (>=1.7.4)',
],
description='Private media file storage for Django projects',
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment