diff --git a/django/middleware/cache.py b/django/middleware/cache.py new file mode 100644 index 0000000000..f3d03e657a --- /dev/null +++ b/django/middleware/cache.py @@ -0,0 +1,88 @@ +from django.conf import settings +from django.core.cache import cache +from django.utils.httpwrappers import HttpResponseNotModified +from django.utils.text import compress_string +import datetime, md5 + +class CacheMiddleware: + """ + Cache middleware. If this is enabled, each Django-powered page will be + cached for CACHE_MIDDLEWARE_SECONDS seconds. Cache is based on URLs. Pages + with GET or POST parameters are not cached. + + If the cache is shared across multiple sites using the same Django + installation, set the CACHE_MIDDLEWARE_KEY_PREFIX to the name of the site, + or some other string that is unique to this Django instance, to prevent key + collisions. + + This middleware will also make the following optimizations: + + * If the CACHE_MIDDLEWARE_GZIP setting is True, the content will be + gzipped. + + * ETags will be added, using a simple MD5 hash of the page's content. + """ + def process_request(self, request): + """ + Checks whether the page is already cached. If it is, returns the cached + version. Also handles ETag stuff. + """ + if request.GET or request.POST: + request._cache_middleware_set_cache = False + return None # Don't bother checking the cache. + + accept_encoding = '' + if settings.CACHE_MIDDLEWARE_GZIP: + try: + accept_encoding = request.META['HTTP_ACCEPT_ENCODING'] + except KeyError: + pass + accepts_gzip = 'gzip' in accept_encoding + request._cache_middleware_accepts_gzip = accepts_gzip + + # This uses the same cache_key as views.decorators.cache.cache_page, + # so the cache can be shared. + cache_key = 'views.decorators.cache.cache_page.%s.%s.%s' % \ + (settings.CACHE_MIDDLEWARE_KEY_PREFIX, request.path, accepts_gzip) + request._cache_middleware_key = cache_key + + response = cache.get(cache_key, None) + if response is None: + request._cache_middleware_set_cache = True + return None + else: + request._cache_middleware_set_cache = False + # Logic is from http://simon.incutio.com/archive/2003/04/23/conditionalGet + try: + if_none_match = request.META['HTTP_IF_NONE_MATCH'] + except KeyError: + if_none_match = None + try: + if_modified_since = request.META['HTTP_IF_MODIFIED_SINCE'] + except KeyError: + if_modified_since = None + if if_none_match is None and if_modified_since is None: + pass + elif if_none_match is not None and response['ETag'] != if_none_match: + pass + elif if_modified_since is not None and response['Last-Modified'] != if_modified_since: + pass + else: + return HttpResponseNotModified() + return response + + def process_response(self, request, response): + """ + Sets the cache, if needed. + """ + if request._cache_middleware_set_cache: + content = response.get_content_as_string('utf-8') + if request._cache_middleware_accepts_gzip: + content = compress_string(content) + response.content = content + response['Content-Encoding'] = 'gzip' + response['ETag'] = md5.new(content).hexdigest() + response['Content-Length'] = '%d' % len(content) + response['Last-Modified'] = datetime.datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT') + cache.set(request._cache_middleware_key, response, settings.CACHE_MIDDLEWARE_SECONDS) + return response