Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Split CacheMiddleware up into two parts -- an update-cache and a fetc…

…h-from-cache middleware. This lets you run each half of the cache middleware at the correct time to avoid bad interactions between the cache middleware and other middleware that must modify the cache key (like the locale middleware).

CacheMiddleware itself is still around for backwards-compatibility and as a hook point for the cache decorator, but the documentation has been updated to point people towards the two-part caching middleware.

Refs #730.


git-svn-id: http://code.djangoproject.com/svn/django/trunk@8260 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 752659037686026e85c2da52c756d4bfec8c4d13 1 parent e8f1864
@jacobian jacobian authored
Showing with 144 additions and 71 deletions.
  1. +115 −57 django/middleware/cache.py
  2. +29 −14 docs/cache.txt
View
172 django/middleware/cache.py
@@ -1,76 +1,72 @@
-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
+"""
+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::
-class CacheMiddleware(object):
- """
- Cache middleware. If this is enabled, each Django-powered page will be
- cached (based on URLs).
+ MIDDLEWARE_CLASSES = [
+ 'django.middleware.cache.UpdateCacheMiddleware',
+ ...
+ 'django.middleware.cache.FetchFromCacheMiddleware'
+ ]
- Only parameter-less GET or HEAD-requests with status code 200 are cached.
+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 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.
+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.
- 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).
+More details about how the caching works:
- This middleware expects that a HEAD request is answered with a response
- exactly like the corresponding GET request.
+* Only parameter-less GET or HEAD-requests with status code 200 are cached.
- When a hit occurs, a shallow copy of the original response object is
- returned from process_request.
+* 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.
- 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.
+* 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 also sets ETag, Last-Modified, Expires and Cache-Control
- headers on the response object.
- """
- 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
+* This middleware expects that a HEAD request is answered with a response
+ exactly like the corresponding GET request.
- 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."
+* When a hit occurs, a shallow copy of the original response object is returned
+ from process_request.
- if not request.method in ('GET', 'HEAD') or request.GET:
- request._cache_update_cache = False
- return None # Don't bother checking the cache.
+* Pages will be cached based on the contents of the request headers listed in
+ the response's "Vary" header.
- if self.cache_anonymous_only and request.user.is_authenticated():
- request._cache_update_cache = False
- return None # Don't cache requests from authenticated users.
+* This middleware also sets ETag, Last-Modified, Expires and Cache-Control
+ headers on the response object.
- 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.
+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
- request._cache_update_cache = False
- return response
+class UpdateCacheMiddleware(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):
+ 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 @@ def process_response(self, request, response):
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
+
View
43 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.

0 comments on commit 7526590

Please sign in to comment.
Something went wrong with that request. Please try again.