Created
January 17, 2016 22:54
-
-
Save thenewguy/a92e05a3568fb6f1cc7c to your computer and use it in GitHub Desktop.
static serve middleware based off whitenoise
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import mimetypes | |
import time | |
from os.path import splitext | |
from django.contrib.staticfiles.storage import staticfiles_storage | |
from django.http import HttpResponse, FileResponse, HttpResponseNotAllowed, Http404 | |
from django.core.exceptions import SuspiciousFileOperation | |
from django.conf import settings | |
from django.utils.six import text_type | |
from django.utils.http import http_date | |
from posixpath import join | |
from wsgiref.headers import Headers | |
mimetypes.init() | |
REQUEST_METHOD_HEAD = 'HEAD' | |
REQUEST_METHOD_GET = 'GET' | |
ALLOWED_REQUEST_METHODS = (REQUEST_METHOD_GET, REQUEST_METHOD_HEAD) | |
MIMETYPES_WITH_CHARSET = frozenset(( | |
'application/javascript', 'application/xml')) | |
class ServeStaticfilesMiddleware(object): | |
allow_all_origins = True | |
max_age_forever = 10*365*24*60*60# Ten years is what nginx sets a max age if you use 'expires max;' | |
max_age_temporary = 60# Let the CDN cache for a short time to reduce load on app servers | |
def process_request(self, request): | |
request_path = request.path_info | |
if self.should_hijack_request(request_path): | |
request_method = request.method | |
if not request_method in ALLOWED_REQUEST_METHODS: | |
return HttpResponseNotAllowed(ALLOWED_REQUEST_METHODS) | |
file_obj = self.find_file(request_path) | |
if file_obj: | |
if request_method == REQUEST_METHOD_HEAD: | |
response = HttpResponse() | |
else: | |
response = FileResponse(file_obj) | |
headers = self.get_response_headers(file_obj, request_path) | |
for key, value in headers.items(): | |
response[key] = value | |
return response | |
else: | |
raise Http404("Could not find file matching '%s'." % request_path) | |
def can_cache_forever(self, file_obj, request_path): | |
name_without_hash = self.get_name_without_hash(file_obj) | |
if file_obj.name == name_without_hash: | |
return False | |
static_url = self.get_static_url(name_without_hash) | |
if static_url and static_url.endswith(request_path): | |
return True | |
return False | |
def get_name_without_hash(self, file_obj): | |
""" | |
Removes the version hash from a filename e.g, transforms | |
'css/application.f3ea4bcc2.css' into 'css/application.css' | |
Note: this is specific to the naming scheme used by Django's | |
CachedStaticFilesStorage. You may have to override this if | |
you are using a different static files versioning system | |
""" | |
name_with_hash, ext = splitext(file_obj.name) | |
name = splitext(name_with_hash)[0] | |
return name + ext | |
def get_static_url(self, name): | |
try: | |
return staticfiles_storage.url(name) | |
except ValueError: | |
return None | |
def should_hijack_request(self, request_path): | |
return request_path.startswith(settings.STATIC_URL) | |
def get_file_name(self, request_path): | |
file_name = request_path[len(settings.STATIC_URL):] | |
self.validate_file_name(file_name) | |
return file_name | |
def validate_file_name(self, file_name): | |
static_path = join(settings.STATIC_ROOT, file_name) | |
if not static_path.startswith(settings.STATIC_ROOT): | |
raise SuspiciousFileOperation("Requested static file '%s' resolved to '%s' which does not reside under static root '%s'" % (request_path, static_path, settings.STATIC_ROOT)) | |
def get_charset(self, mimetype): | |
if (mimetype.startswith('text/') | |
or mimetype in MIMETYPES_WITH_CHARSET): | |
return 'utf-8' | |
def get_response_headers(self, file_obj, request_path): | |
headers = Headers([]) | |
mimetype = mimetypes.guess_type(file_obj.name)[0] or "application/octet-stream" | |
charset = self.get_charset(mimetype) | |
params = {'charset': charset} if charset else {} | |
headers.add_header('Content-Type', mimetype, **params) | |
headers.add_header('Content-Length', text_type(file_obj.size)) | |
try: | |
last_modified = staticfiles_storage.modified_time(file_obj.name) | |
except NotImplementedError: | |
pass | |
else: | |
headers.add_header('Last-Modified', http_date(time.mktime(last_modified.timetuple()))) | |
if self.allow_all_origins: | |
headers.add_header('Access-Control-Allow-Origin', '*') | |
if self.can_cache_forever(file_obj, request_path): | |
max_age = self.max_age_forever | |
else: | |
max_age = self.max_age_temporary | |
if max_age is not None: | |
cache_control = 'public, max-age={0}'.format(max_age) | |
headers['Cache-Control'] = cache_control | |
return headers | |
def find_file(self, request_path): | |
try: | |
file_obj = staticfiles_storage.open(self.get_file_name(request_path), "rb") | |
except IOError: | |
file_obj = None | |
return file_obj |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment