Skip to content

Instantly share code, notes, and snippets.

@thenewguy
Created January 17, 2016 22:54
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thenewguy/a92e05a3568fb6f1cc7c to your computer and use it in GitHub Desktop.
Save thenewguy/a92e05a3568fb6f1cc7c to your computer and use it in GitHub Desktop.
static serve middleware based off whitenoise
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