diff --git a/django/middleware/cache.py b/django/middleware/cache.py index 23a907f8be..7301fdc005 100644 --- a/django/middleware/cache.py +++ b/django/middleware/cache.py @@ -1,76 +1,72 @@ +""" +Cache middleware. If enabled, each Django-powered page will be cached based on +URL. The cannonical way to enable cache middleware is to set +``UpdateCacheMiddleware`` as your first piece of middleware, and +``FetchFromCacheMiddleware`` as the last:: + + MIDDLEWARE_CLASSES = [ + 'django.middleware.cache.UpdateCacheMiddleware', + ... + 'django.middleware.cache.FetchFromCacheMiddleware' + ] + +This is counter-intuitive, but correct: ``UpdateCacheMiddleware`` needs to run +last during the response phase, which processes middleware bottom-up; +``FetchFromCacheMiddleware`` needs to run last during the request phase, which +processes middleware top-down. + +The single-class ``CacheMiddleware`` can be used for some simple sites. However, +if any other peice of middleware needs to affect the cache key, you'll need to +use the two-part UpdateCacheMiddleware and FetchFromCacheMiddleware. This'll +most often happen when you're using Django's LocaleMiddleware. + +More details about how the caching works: + +* Only parameter-less GET or HEAD-requests with status code 200 are cached. + +* The number of seconds each page is stored for is set by the "max-age" section + of the response's "Cache-Control" header, falling back to the + CACHE_MIDDLEWARE_SECONDS setting if the section was not found. + +* If CACHE_MIDDLEWARE_ANONYMOUS_ONLY is set to True, only anonymous requests + (i.e., those not made by a logged-in user) will be cached. This is a simple + and effective way of avoiding the caching of the Django admin (and any other + user-specific content). + +* This middleware expects that a HEAD request is answered with a response + exactly like the corresponding GET request. + +* When a hit occurs, a shallow copy of the original response object is returned + from process_request. + +* Pages will be cached based on the contents of the request headers listed in + the response's "Vary" header. + +* This middleware also sets ETag, Last-Modified, Expires and Cache-Control + headers on the response object. + +""" + from django.conf import settings from django.core.cache import cache from django.utils.cache import get_cache_key, learn_cache_key, patch_response_headers, get_max_age -class CacheMiddleware(object): +class UpdateCacheMiddleware(object): """ - Cache middleware. If this is enabled, each Django-powered page will be - cached (based on URLs). - - Only parameter-less GET or HEAD-requests with status code 200 are cached. - - The number of seconds each page is stored for is set by the - "max-age" section of the response's "Cache-Control" header, falling back to - the CACHE_MIDDLEWARE_SECONDS setting if the section was not found. - - If CACHE_MIDDLEWARE_ANONYMOUS_ONLY is set to True, only anonymous requests - (i.e., those not made by a logged-in user) will be cached. This is a - simple and effective way of avoiding the caching of the Django admin (and - any other user-specific content). - - This middleware expects that a HEAD request is answered with a response - exactly like the corresponding GET request. - - When a hit occurs, a shallow copy of the original response object is - returned from process_request. - - Pages will be cached based on the contents of the request headers - listed in the response's "Vary" header. This means that pages shouldn't - change their "Vary" header. - - This middleware also sets ETag, Last-Modified, Expires and Cache-Control - headers on the response object. + Response-phase cache middleware that updates the cache if the response is + cacheable. + + Must be used as part of the two-part update/fetch cache middleware. + UpdateCacheMiddleware must be the first piece of middleware in + MIDDLEWARE_CLASSES so that it'll get called last during the response phase. """ - def __init__(self, cache_timeout=None, key_prefix=None, cache_anonymous_only=None): - self.cache_timeout = cache_timeout - if cache_timeout is None: - self.cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS - self.key_prefix = key_prefix - if key_prefix is None: - self.key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX - if cache_anonymous_only is None: - self.cache_anonymous_only = getattr(settings, 'CACHE_MIDDLEWARE_ANONYMOUS_ONLY', False) - else: - self.cache_anonymous_only = cache_anonymous_only - - def process_request(self, request): - "Checks whether the page is already cached and returns the cached version if available." - if self.cache_anonymous_only: - 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." - - if not request.method in ('GET', 'HEAD') or request.GET: - request._cache_update_cache = False - return None # Don't bother checking the cache. - - if self.cache_anonymous_only and request.user.is_authenticated(): - request._cache_update_cache = False - return None # Don't cache requests from authenticated users. - - cache_key = get_cache_key(request, self.key_prefix) - if cache_key is None: - request._cache_update_cache = True - return None # No cache information available, need to rebuild. - - response = cache.get(cache_key, None) - if response is None: - request._cache_update_cache = True - return None # No cache information available, need to rebuild. - - request._cache_update_cache = False - return response + def __init__(self): + self.cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS + self.key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX + self.cache_anonymous_only = getattr(settings, 'CACHE_MIDDLEWARE_ANONYMOUS_ONLY', False) def process_response(self, request, response): - "Sets the cache, if needed." + """Sets the cache, if needed.""" if not hasattr(request, '_cache_update_cache') or not request._cache_update_cache: # We don't need to update the cache, just return. return response @@ -95,3 +91,65 @@ class CacheMiddleware(object): cache_key = learn_cache_key(request, response, timeout, self.key_prefix) cache.set(cache_key, response, timeout) return response + +class FetchFromCacheMiddleware(object): + """ + Request-phase cache middleware that fetches a page from the cache. + + Must be used as part of the two-part update/fetch cache middleware. + FetchFromCacheMiddleware must be the last piece of middleware in + MIDDLEWARE_CLASSES so that it'll get called last during the request phase. + """ + def __init__(self): + self.cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS + self.key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX + self.cache_anonymous_only = getattr(settings, 'CACHE_MIDDLEWARE_ANONYMOUS_ONLY', False) + + def process_request(self, request): + """ + Checks whether the page is already cached and returns the cached + version if available. + """ + if self.cache_anonymous_only: + 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." + + if not request.method in ('GET', 'HEAD') or request.GET: + request._cache_update_cache = False + return None # Don't bother checking the cache. + + if self.cache_anonymous_only and request.user.is_authenticated(): + request._cache_update_cache = False + return None # Don't cache requests from authenticated users. + + cache_key = get_cache_key(request, self.key_prefix) + if cache_key is None: + request._cache_update_cache = True + return None # No cache information available, need to rebuild. + + response = cache.get(cache_key, None) + if response is None: + request._cache_update_cache = True + return None # No cache information available, need to rebuild. + + request._cache_update_cache = False + return response + +class CacheMiddleware(UpdateCacheMiddleware, FetchFromCacheMiddleware): + """ + Cache middleware that provides basic behavior for many simple sites. + + Also used as the hook point for the cache decorator, which is generated + using the decorator-from-middleware utility. + """ + def __init__(self, cache_timeout=None, key_prefix=None, cache_anonymous_only=None): + self.cache_timeout = cache_timeout + if cache_timeout is None: + self.cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS + self.key_prefix = key_prefix + if key_prefix is None: + self.key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX + if cache_anonymous_only is None: + self.cache_anonymous_only = getattr(settings, 'CACHE_MIDDLEWARE_ANONYMOUS_ONLY', False) + else: + self.cache_anonymous_only = cache_anonymous_only + diff --git a/docs/cache.txt b/docs/cache.txt index d22dd91994..4ca9cbef72 100644 --- a/docs/cache.txt +++ b/docs/cache.txt @@ -231,17 +231,25 @@ arguments. The per-site cache ================== +**New in Django development version** (previous versions of Django only provided a single ``CacheMiddleware`` instead of the two pieces described below). + Once the cache is set up, the simplest way to use caching is to cache your -entire site. Just add ``'django.middleware.cache.CacheMiddleware'`` to your +entire site. You'll need to add +``'django.middleware.cache.UpdateCacheMiddleware'`` and +``'django.middleware.cache.FetchFromCacheMiddleware' to your ``MIDDLEWARE_CLASSES`` setting, as in this example:: MIDDLEWARE_CLASSES = ( - 'django.middleware.cache.CacheMiddleware', + 'django.middleware.cache.UpdateCacheMiddleware', 'django.middleware.common.CommonMiddleware', + 'django.middleware.cache.FetchFromCacheMiddleware', ) -(The order of ``MIDDLEWARE_CLASSES`` matters. See `Order of MIDDLEWARE_CLASSES`_ -below.) +.. note:: + + No, that's not a typo: the "update" middleware must be first in the list, + and the "fetch" middleware must be last. The details are a bit obscure, but + see `Order of MIDDLEWARE_CLASSES`_ below if you'd like the full story. Then, add the following required settings to your Django settings file: @@ -258,10 +266,9 @@ parameters. Optionally, if the ``CACHE_MIDDLEWARE_ANONYMOUS_ONLY`` setting is will be cached. This is a simple and effective way of disabling caching for any user-specific pages (include Django's admin interface). Note that if you use ``CACHE_MIDDLEWARE_ANONYMOUS_ONLY``, you should make sure you've activated -``AuthenticationMiddleware`` and that ``AuthenticationMiddleware`` appears -before ``CacheMiddleware`` in your ``MIDDLEWARE_CLASSES``. +``AuthenticationMiddleware``. -Additionally, ``CacheMiddleware`` automatically sets a few headers in each +Additionally, the cache middleware automatically sets a few headers in each ``HttpResponse``: * Sets the ``Last-Modified`` header to the current date/time when a fresh @@ -627,16 +634,24 @@ apps' performance: Order of MIDDLEWARE_CLASSES =========================== -If you use ``CacheMiddleware``, it's important to put it in the right place -within the ``MIDDLEWARE_CLASSES`` setting, because the cache middleware needs -to know which headers by which to vary the cache storage. Middleware always -adds something to the ``Vary`` response header when it can. +If you use caching middlewaare, it's important to put each half in the right +place within the ``MIDDLEWARE_CLASSES`` setting. That's because the cache +middleware needs to know which headers by which to vary the cache storage. +Middleware always adds something to the ``Vary`` response header when it can. -Put the ``CacheMiddleware`` *before* any other middleware that might add -something to the ``Vary`` header (response middleware is applied in reverse -order). The following middleware modules do so: +``UpdateCacheMiddleware`` runs during the response phase, where middleware is +run in reverse order, so an item at the top of the list runs *last* during the +response phase. Thus, you need to make sure that ``UpdateCacheMiddleware`` +appears *before* any other middleware that might add something to the ``Vary`` +header. The following middleware modules do so: * ``SessionMiddleware`` adds ``Cookie`` * ``GZipMiddleware`` adds ``Accept-Encoding`` * ``LocaleMiddleware`` adds ``Accept-Language`` + +``FetchFromCacheMiddleware``, on the other hand, runs during the request phase, +where middleware is applied first-to-last, so an item at the top of the list +runs *first* during the request phase. The ``FetchFromCacheMiddleware`` also +needs to run after other middleware updates the ``Vary`` header, so +``FetchFromCacheMiddleware`` must be *after* any item that does so.