Split CacheMiddleware up into two parts -- an update-cache and a fetch-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
This commit is contained in:
Jacob Kaplan-Moss 2008-08-09 15:07:45 +00:00
parent e8f18643cf
commit 7526590376
2 changed files with 152 additions and 79 deletions

View File

@ -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

View File

@ -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.