Created
March 28, 2013 12:34
-
-
Save manfre/5262782 to your computer and use it in GitHub Desktop.
Django middleware to handle arbitrary Models that share common URL space. Found URLs matches will be cached and short circuited on subsequent requests.
This file contains 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 logging, os, os.path, re | |
from hashlib import md5 | |
from django.http import Http404, HttpResponse, HttpResponseForbidden, HttpResponseNotFound, HttpResponseRedirect, HttpResponseNotModified | |
from django.conf import settings | |
from django.core.servers.basehttp import FileWrapper | |
from django.core.urlresolvers import reverse, NoReverseMatch | |
from django.utils.encoding import smart_str | |
from django.utils.http import http_date | |
from django.core.cache import cache | |
from django.core.exceptions import PermissionDenied | |
from django.db.models.signals import post_save, post_delete | |
from web.srcutil.cache import cache_key | |
from web.cms import models as cms | |
import mimetypes | |
mimetypes.init() | |
logger = logging.getLogger('src.cms.middleware') | |
class ContentHandledNoResponse(Exception): | |
""" | |
Exception is used by ContentMiddleware to flag a URL as matching a | |
pattern that can only be handled by a specific middlware, but there | |
is currently no content. | |
""" | |
pass | |
class ContentHandledWithWarning(Exception): | |
""" | |
Exception is used by ContentMiddleware to notify that a requested | |
asset may exist, but the handling middleware raised an exception. | |
""" | |
pass | |
class ContentMiddleware(object): | |
excluded_path_patterns = [] | |
timeout_errored = 600 | |
timeout_handled = 3600 | |
timeout_not_handled = 3600 | |
def __init__(self): | |
excludes = [ | |
'^/admin/', | |
'^' + settings.MEDIA_URL, | |
'^' + settings.STATIC_URL, | |
] + self.excluded_path_patterns | |
pattern = '|'.join(excludes) | |
try: | |
self.exclusion_re = re.compile( | |
pattern, | |
re.IGNORECASE | |
) | |
except OverflowError: | |
logger.exception(pattern) | |
raise | |
@classmethod | |
def cache_key(cls, url): | |
""" | |
Return a cache key unique for the URL and the middleware. | |
""" | |
return 'ContentMiddleware-{0}'.format( | |
md5('{0}{1}'.format(cls.__name__, smart_str(url))).hexdigest(), | |
) | |
def process(self, request): | |
raise NotImplemented() | |
def _process(self, request): | |
timeout = self.timeout_handled | |
handled = True | |
response = None | |
try: | |
response = self.process(request) | |
except Http404: | |
# URL is not currently not handled, but could change | |
handled = False | |
timeout = self.timeout_not_handled | |
except (PermissionDenied, ContentHandledNoResponse): | |
# URL is Handled by current middleware | |
pass | |
except ContentHandledWithWarning as e: | |
# This is handled, but keeping a smaller cache time in | |
# case it goes away | |
timeout = self.timeout_errored | |
logger.warning(unicode(e)) | |
except Exception as e: | |
# unexpected error | |
timeout = self.timeout_errored | |
logger.error('Caught exception in {0}'.format(self.__class__.__name__), | |
extra={'request': request}, exc_info=True) | |
if settings.DEBUG: | |
raise | |
logger.debug(u'url={0}, handled={1}, timeout={2}, class={3}'.format( | |
request.path, handled, timeout, self.__class__, | |
)) | |
key = self.cache_key(request.path) | |
cache.add(key, handled, timeout) | |
return response | |
def process_request(self, request): | |
if self.exclusion_re.match(request.path): | |
return None | |
exists = cache.get(self.cache_key(request.path), False) | |
# shortcircuit handle known url | |
if exists: | |
return self._process(request) | |
def process_response(self, request, response): | |
if response.status_code != 404 or self.exclusion_re.match(request.path): | |
return response | |
exists = cache.get(self.cache_key(request.path), None) | |
if exists or exists is None: | |
response = self._process(request) or response | |
return response | |
@classmethod | |
def cache_delete_callback(cls, sender, **kwargs): | |
""" | |
post_save & post_delete signal handler that deletes the cached url | |
information used by this (and child) middleware. | |
""" | |
obj = kwargs.get('instance', None) | |
try: | |
url = obj.get_absolute_url() | |
except: | |
url = None | |
if not url: | |
return | |
key = cls.cache_key(url) | |
cache.delete(key) | |
class ManualPageMiddleware(ContentMiddleware): | |
excluded_path_patterns = [ | |
'.*\.(\w{1,4})$', # exclude URL with a file extension | |
'^/image/', # manual pages should never be under /image | |
] | |
def process(self, request): | |
from web.cms.views.manualpage import view_manualpage | |
return view_manualpage(request) | |
post_save.connect(ManualPageMiddleware.cache_delete_callback, sender=cms.ManualPage) | |
post_delete.connect(ManualPageMiddleware.cache_delete_callback, sender=cms.ManualPage) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment