summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--django/middleware/cache.py21
-rw-r--r--django/utils/cache.py14
-rw-r--r--docs/topics/cache.txt4
-rw-r--r--tests/regressiontests/cache/tests.py69
4 files changed, 86 insertions, 22 deletions
diff --git a/django/middleware/cache.py b/django/middleware/cache.py
index 3f602fe652..a3076acd22 100644
--- a/django/middleware/cache.py
+++ b/django/middleware/cache.py
@@ -34,8 +34,8 @@ More details about how the caching works:
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.
+* This middleware expects that a HEAD request is answered with the same response
+ headers exactly like the corresponding GET request.
* When a hit occurs, a shallow copy of the original response object is returned
from process_request.
@@ -71,12 +71,6 @@ class UpdateCacheMiddleware(object):
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
- if request.method != 'GET':
- # This is a stronger requirement than above. It is needed
- # because of interactions between this middleware and the
- # HTTPMiddleware, which throws the body of a HEAD-request
- # away before this middleware gets a chance to cache it.
- return response
if not response.status_code == 200:
return response
# Try to get the timeout from the "max-age" section of the "Cache-
@@ -123,16 +117,25 @@ class FetchFromCacheMiddleware(object):
request._cache_update_cache = False
return None # Don't cache requests from authenticated users.
- cache_key = get_cache_key(request, self.key_prefix)
+ # try and get the cached GET response
+ cache_key = get_cache_key(request, self.key_prefix, 'GET')
+
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 it wasn't found and we are looking for a HEAD, try looking just for that
+ if response is None and request.method == 'HEAD':
+ cache_key = get_cache_key(request, self.key_prefix, 'HEAD')
+ response = cache.get(cache_key, None)
+
if response is None:
request._cache_update_cache = True
return None # No cache information available, need to rebuild.
+ # hit, return cached response
request._cache_update_cache = False
return response
diff --git a/django/utils/cache.py b/django/utils/cache.py
index 6cfd893668..b1b35b5a05 100644
--- a/django/utils/cache.py
+++ b/django/utils/cache.py
@@ -143,7 +143,7 @@ def _i18n_cache_key_suffix(request, cache_key):
cache_key += '.%s' % getattr(request, 'LANGUAGE_CODE', get_language())
return cache_key
-def _generate_cache_key(request, headerlist, key_prefix):
+def _generate_cache_key(request, method, headerlist, key_prefix):
"""Returns a cache key from the headers given in the header list."""
ctx = md5_constructor()
for header in headerlist:
@@ -151,8 +151,8 @@ def _generate_cache_key(request, headerlist, key_prefix):
if value is not None:
ctx.update(value)
path = md5_constructor(iri_to_uri(request.path))
- cache_key = 'views.decorators.cache.cache_page.%s.%s.%s' % (
- key_prefix, path.hexdigest(), ctx.hexdigest())
+ cache_key = 'views.decorators.cache.cache_page.%s.%s.%s.%s' % (
+ key_prefix, request.method, path.hexdigest(), ctx.hexdigest())
return _i18n_cache_key_suffix(request, cache_key)
def _generate_cache_header_key(key_prefix, request):
@@ -162,7 +162,7 @@ def _generate_cache_header_key(key_prefix, request):
key_prefix, path.hexdigest())
return _i18n_cache_key_suffix(request, cache_key)
-def get_cache_key(request, key_prefix=None):
+def get_cache_key(request, key_prefix=None, method='GET'):
"""
Returns a cache key based on the request path. It can be used in the
request phase because it pulls the list of headers to take into account
@@ -177,7 +177,7 @@ def get_cache_key(request, key_prefix=None):
cache_key = _generate_cache_header_key(key_prefix, request)
headerlist = cache.get(cache_key, None)
if headerlist is not None:
- return _generate_cache_key(request, headerlist, key_prefix)
+ return _generate_cache_key(request, method, headerlist, key_prefix)
else:
return None
@@ -203,12 +203,12 @@ def learn_cache_key(request, response, cache_timeout=None, key_prefix=None):
headerlist = ['HTTP_'+header.upper().replace('-', '_')
for header in cc_delim_re.split(response['Vary'])]
cache.set(cache_key, headerlist, cache_timeout)
- return _generate_cache_key(request, headerlist, key_prefix)
+ return _generate_cache_key(request, request.method, headerlist, key_prefix)
else:
# if there is no Vary header, we still need a cache key
# for the request.path
cache.set(cache_key, [], cache_timeout)
- return _generate_cache_key(request, [], key_prefix)
+ return _generate_cache_key(request, request.method, [], key_prefix)
def _to_tuple(s):
diff --git a/docs/topics/cache.txt b/docs/topics/cache.txt
index 5797199411..f2b7a594d3 100644
--- a/docs/topics/cache.txt
+++ b/docs/topics/cache.txt
@@ -328,7 +328,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``.
+``AuthenticationMiddleware``. The cache middleware expects that a HEAD request
+is answered with the same response headers exactly like the corresponding GET
+request, in that case it could return cached GET response for HEAD request.
Additionally, the cache middleware automatically sets a few headers in each
``HttpResponse``:
diff --git a/tests/regressiontests/cache/tests.py b/tests/regressiontests/cache/tests.py
index a91b842ebd..9818b03661 100644
--- a/tests/regressiontests/cache/tests.py
+++ b/tests/regressiontests/cache/tests.py
@@ -507,12 +507,13 @@ class CacheUtils(unittest.TestCase):
settings.CACHE_MIDDLEWARE_SECONDS = self.old_middleware_seconds
settings.USE_I18N = self.orig_use_i18n
- def _get_request(self, path):
+ def _get_request(self, path, method='GET'):
request = HttpRequest()
request.META = {
'SERVER_NAME': 'testserver',
'SERVER_PORT': 80,
}
+ request.method = method
request.path = request.path_info = "/cache/%s" % path
return request
@@ -544,18 +545,76 @@ class CacheUtils(unittest.TestCase):
self.assertEqual(get_cache_key(request), None)
# Set headers to an empty list.
learn_cache_key(request, response)
- self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
+ self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.GET.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
# Verify that a specified key_prefix is taken in to account.
learn_cache_key(request, response, key_prefix=key_prefix)
- self.assertEqual(get_cache_key(request, key_prefix=key_prefix), 'views.decorators.cache.cache_page.localprefix.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
+ self.assertEqual(get_cache_key(request, key_prefix=key_prefix), 'views.decorators.cache.cache_page.localprefix.GET.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
def test_learn_cache_key(self):
- request = self._get_request(self.path)
+ request = self._get_request(self.path, 'HEAD')
response = HttpResponse()
response['Vary'] = 'Pony'
# Make sure that the Vary header is added to the key hash
learn_cache_key(request, response)
- self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
+ self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.HEAD.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
+
+class CacheHEADTest(unittest.TestCase):
+
+ def setUp(self):
+ self.orig_cache_middleware_seconds = settings.CACHE_MIDDLEWARE_SECONDS
+ self.orig_cache_middleware_key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
+ self.orig_cache_backend = settings.CACHE_BACKEND
+ settings.CACHE_MIDDLEWARE_SECONDS = 60
+ settings.CACHE_MIDDLEWARE_KEY_PREFIX = 'test'
+ settings.CACHE_BACKEND = 'locmem:///'
+ self.path = '/cache/test/'
+
+ def tearDown(self):
+ settings.CACHE_MIDDLEWARE_SECONDS = self.orig_cache_middleware_seconds
+ settings.CACHE_MIDDLEWARE_KEY_PREFIX = self.orig_cache_middleware_key_prefix
+ settings.CACHE_BACKEND = self.orig_cache_backend
+
+ def _get_request(self, method):
+ request = HttpRequest()
+ request.META = {
+ 'SERVER_NAME': 'testserver',
+ 'SERVER_PORT': 80,
+ }
+ request.method = method
+ request.path = request.path_info = self.path
+ return request
+
+ def _get_request_cache(self, method):
+ request = self._get_request(method)
+ request._cache_update_cache = True
+ return request
+
+ def _set_cache(self, request, msg):
+ response = HttpResponse()
+ response.content = msg
+ return UpdateCacheMiddleware().process_response(request, response)
+
+ def test_head_caches_correctly(self):
+ test_content = 'test content'
+
+ request = self._get_request_cache('HEAD')
+ self._set_cache(request, test_content)
+
+ request = self._get_request('HEAD')
+ get_cache_data = FetchFromCacheMiddleware().process_request(request)
+ self.assertNotEqual(get_cache_data, None)
+ self.assertEqual(test_content, get_cache_data.content)
+
+ def test_head_with_cached_get(self):
+ test_content = 'test content'
+
+ request = self._get_request_cache('GET')
+ self._set_cache(request, test_content)
+
+ request = self._get_request('HEAD')
+ get_cache_data = FetchFromCacheMiddleware().process_request(request)
+ self.assertNotEqual(get_cache_data, None)
+ self.assertEqual(test_content, get_cache_data.content)
class CacheI18nTest(unittest.TestCase):