/ella/core/middleware.py

https://github.com/joskid/ella · Python · 166 lines · 107 code · 30 blank · 29 comment · 22 complexity · aa35dd76f5e17825da8483afb588afe7 MD5 · raw file

  1. import time
  2. import re
  3. import logging
  4. log = logging.getLogger('ella.core.middleware')
  5. from django import template
  6. from django.middleware.cache import CacheMiddleware as DjangoCacheMiddleware
  7. from django.core.cache import cache
  8. from django.utils.cache import get_cache_key, add_never_cache_headers, learn_cache_key
  9. from django.conf import settings
  10. from ella.core.conf import core_settings
  11. class DoubleRenderMiddleware(object):
  12. def _get_excluded_urls(self):
  13. if hasattr(self, '_excluded_urls'):
  14. return self._excluded_urls
  15. if core_settings.DOUBLE_RENDER_EXCLUDE_URLS is None:
  16. self._excluded_urls = None
  17. return None
  18. self._excluded_urls = ()
  19. for url in core_settings.DOUBLE_RENDER_EXCLUDE_URLS:
  20. self._excluded_urls += (re.compile(url),)
  21. return self._excluded_urls
  22. def process_response(self, request, response):
  23. if response.status_code != 200 \
  24. or not response['Content-Type'].startswith('text') \
  25. or not core_settings.DOUBLE_RENDER:
  26. return response
  27. if self._get_excluded_urls() is not None:
  28. for pattern in self._get_excluded_urls():
  29. if pattern.match(request.path):
  30. return response
  31. try:
  32. c = template.RequestContext(request, {'SECOND_RENDER': True})
  33. t = template.Template(response.content)
  34. response.content = t.render(c)
  35. response['Content-Length'] = len(response.content)
  36. except Exception, e:
  37. log.warning('Failed to double render on (%s)', unicode(e).encode('utf8'))
  38. return response
  39. class CacheMiddleware(DjangoCacheMiddleware):
  40. def process_request(self, request):
  41. resp = super(CacheMiddleware, self).process_request(request)
  42. if resp is None:
  43. request._cache_middleware_key = get_cache_key(request, self.key_prefix)
  44. return resp
  45. def process_response(self, request, response):
  46. resp = super(CacheMiddleware, self).process_response(request, response)
  47. # never cache headers + ETag
  48. add_never_cache_headers(resp)
  49. return resp
  50. class UpdateCacheMiddleware(object):
  51. """
  52. Response-phase cache middleware that updates the cache if the response is
  53. cacheable.
  54. Must be used as part of the two-part update/fetch cache middleware.
  55. UpdateCacheMiddleware must be the first piece of middleware in
  56. MIDDLEWARE_CLASSES so that it'll get called last during the response phase.
  57. """
  58. def __init__(self):
  59. self.cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS
  60. self.key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
  61. self.cache_anonymous_only = getattr(settings, 'CACHE_MIDDLEWARE_ANONYMOUS_ONLY', False)
  62. def process_response(self, request, response):
  63. """Sets the cache, if needed."""
  64. # never cache headers + ETag
  65. add_never_cache_headers(response)
  66. if not hasattr(request, '_cache_update_cache') or not request._cache_update_cache:
  67. # We don't need to update the cache, just return.
  68. return response
  69. if request.method != 'GET':
  70. # This is a stronger requirement than above. It is needed
  71. # because of interactions between this middleware and the
  72. # HTTPMiddleware, which throws the body of a HEAD-request
  73. # away before this middleware gets a chance to cache it.
  74. return response
  75. if not response.status_code == 200:
  76. return response
  77. # use the precomputed cache_key
  78. if request._cache_middleware_key:
  79. cache_key = request._cache_middleware_key
  80. else:
  81. cache_key = learn_cache_key(request, response, self.cache_timeout, self.key_prefix)
  82. # include the orig_time information within the cache
  83. cache.set(cache_key, (time.time(), response), self.cache_timeout)
  84. return response
  85. class FetchFromCacheMiddleware(object):
  86. """
  87. Request-phase cache middleware that fetches a page from the cache.
  88. Must be used as part of the two-part update/fetch cache middleware.
  89. FetchFromCacheMiddleware must be the last piece of middleware in
  90. MIDDLEWARE_CLASSES so that it'll get called last during the request phase.
  91. """
  92. def __init__(self):
  93. self.cache_expire_timeout = settings.CACHE_MIDDLEWARE_SECONDS
  94. self.key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
  95. self.cache_refresh_timeout = getattr(settings, 'CACHE_MIDDLEWARE_REFRESH_SECONDS', self.cache_expire_timeout / 2)
  96. self.timeout = getattr(settings, 'CACHE_MIDDLEWARE_REFRESH_TIMEOUT', 10)
  97. self.cache_anonymous_only = getattr(settings, 'CACHE_MIDDLEWARE_ANONYMOUS_ONLY', False)
  98. def process_request(self, request):
  99. """
  100. Checks whether the page is already cached and returns the cached
  101. version if available.
  102. """
  103. if self.cache_anonymous_only:
  104. assert hasattr(request, 'user'), "The Django cache middleware with CACHE_MIDDLEWARE_ANONYMOUS_ONLY=True requires authentication middleware to be installed. Edit your MIDDLEWARE_CLASSES setting to insert 'django.contrib.auth.middleware.AuthenticationMiddleware' before the CacheMiddleware."
  105. if not request.method in ('GET', 'HEAD') or request.GET:
  106. request._cache_update_cache = False
  107. return None # Don't bother checking the cache.
  108. if self.cache_anonymous_only and request.user.is_authenticated():
  109. request._cache_update_cache = False
  110. return None # Don't cache requests from authenticated users.
  111. cache_key = get_cache_key(request, self.key_prefix)
  112. request._cache_middleware_key = cache_key
  113. if cache_key is None:
  114. request._cache_update_cache = True
  115. return None # No cache information available, need to rebuild.
  116. response = cache.get(cache_key, None)
  117. if response is None:
  118. request._cache_update_cache = True
  119. return None # No cache information available, need to rebuild.
  120. orig_time, response = response
  121. # time to refresh the cache
  122. if orig_time and ((time.time() - orig_time) > self.cache_refresh_timeout):
  123. request._cache_update_cache = True
  124. # keep the response in the cache for just self.timeout seconds and mark it for update
  125. # other requests will continue werving this response from cache while I alone work on refreshing it
  126. cache.set(cache_key, (None, response), self.timeout)
  127. return None
  128. request._cache_update_cache = False
  129. return response