Fixed #20346 -- Made cache middleware vary on the full URL.
Previously, only the URL path was included in the cache key. Thanks jamey for the suggestion.
This commit is contained in:
parent
280c1a65cc
commit
71a03e01aa
|
@ -191,25 +191,25 @@ def _generate_cache_key(request, method, headerlist, key_prefix):
|
||||||
value = request.META.get(header, None)
|
value = request.META.get(header, None)
|
||||||
if value is not None:
|
if value is not None:
|
||||||
ctx.update(force_bytes(value))
|
ctx.update(force_bytes(value))
|
||||||
path = hashlib.md5(force_bytes(iri_to_uri(request.get_full_path())))
|
url = hashlib.md5(force_bytes(iri_to_uri(request.build_absolute_uri())))
|
||||||
cache_key = 'views.decorators.cache.cache_page.%s.%s.%s.%s' % (
|
cache_key = 'views.decorators.cache.cache_page.%s.%s.%s.%s' % (
|
||||||
key_prefix, method, path.hexdigest(), ctx.hexdigest())
|
key_prefix, method, url.hexdigest(), ctx.hexdigest())
|
||||||
return _i18n_cache_key_suffix(request, cache_key)
|
return _i18n_cache_key_suffix(request, cache_key)
|
||||||
|
|
||||||
|
|
||||||
def _generate_cache_header_key(key_prefix, request):
|
def _generate_cache_header_key(key_prefix, request):
|
||||||
"""Returns a cache key for the header cache."""
|
"""Returns a cache key for the header cache."""
|
||||||
path = hashlib.md5(force_bytes(iri_to_uri(request.get_full_path())))
|
url = hashlib.md5(force_bytes(iri_to_uri(request.build_absolute_uri())))
|
||||||
cache_key = 'views.decorators.cache.cache_header.%s.%s' % (
|
cache_key = 'views.decorators.cache.cache_header.%s.%s' % (
|
||||||
key_prefix, path.hexdigest())
|
key_prefix, url.hexdigest())
|
||||||
return _i18n_cache_key_suffix(request, cache_key)
|
return _i18n_cache_key_suffix(request, cache_key)
|
||||||
|
|
||||||
|
|
||||||
def get_cache_key(request, key_prefix=None, method='GET', cache=None):
|
def get_cache_key(request, key_prefix=None, method='GET', cache=None):
|
||||||
"""
|
"""
|
||||||
Returns a cache key based on the request path and query. It can be used
|
Returns a cache key based on the request URL and query. It can be used
|
||||||
in the request phase because it pulls the list of headers to take into
|
in the request phase because it pulls the list of headers to take into
|
||||||
account from the global path registry and uses those to build a cache key
|
account from the global URL registry and uses those to build a cache key
|
||||||
to check against.
|
to check against.
|
||||||
|
|
||||||
If there is no headerlist stored, the page needs to be rebuilt, so this
|
If there is no headerlist stored, the page needs to be rebuilt, so this
|
||||||
|
@ -229,9 +229,9 @@ def get_cache_key(request, key_prefix=None, method='GET', cache=None):
|
||||||
|
|
||||||
def learn_cache_key(request, response, cache_timeout=None, key_prefix=None, cache=None):
|
def learn_cache_key(request, response, cache_timeout=None, key_prefix=None, cache=None):
|
||||||
"""
|
"""
|
||||||
Learns what headers to take into account for some request path from the
|
Learns what headers to take into account for some request URL from the
|
||||||
response object. It stores those headers in a global path registry so that
|
response object. It stores those headers in a global URL registry so that
|
||||||
later access to that path will know what headers to take into account
|
later access to that URL will know what headers to take into account
|
||||||
without building the response object itself. The headers are named in the
|
without building the response object itself. The headers are named in the
|
||||||
Vary header of the response, but we want to prevent response generation.
|
Vary header of the response, but we want to prevent response generation.
|
||||||
|
|
||||||
|
@ -264,7 +264,7 @@ def learn_cache_key(request, response, cache_timeout=None, key_prefix=None, cach
|
||||||
return _generate_cache_key(request, request.method, headerlist, key_prefix)
|
return _generate_cache_key(request, request.method, headerlist, key_prefix)
|
||||||
else:
|
else:
|
||||||
# if there is no Vary header, we still need a cache key
|
# if there is no Vary header, we still need a cache key
|
||||||
# for the request.get_full_path()
|
# for the request.build_absolute_uri()
|
||||||
cache.set(cache_key, [], cache_timeout)
|
cache.set(cache_key, [], cache_timeout)
|
||||||
return _generate_cache_key(request, request.method, [], key_prefix)
|
return _generate_cache_key(request, request.method, [], key_prefix)
|
||||||
|
|
||||||
|
|
|
@ -622,6 +622,19 @@ cache-specific errors. This has been fixed in Django 1.7, see
|
||||||
|
|
||||||
.. _Ticket #21200: https://code.djangoproject.com/ticket/21200
|
.. _Ticket #21200: https://code.djangoproject.com/ticket/21200
|
||||||
|
|
||||||
|
Cache keys are now generated from the request's absolute URL
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Previous versions of Django generated cache keys using a request's path and
|
||||||
|
query string but not the scheme or host. If a Django application was serving
|
||||||
|
multiple subdomains or domains, cache keys could collide. In Django 1.7, cache
|
||||||
|
keys vary by the absolute URL of the request including scheme, host, path, and
|
||||||
|
query string. For example, the URL portion of a cache key is now generated from
|
||||||
|
``http://www.example.com/path/to/?key=val`` rather than ``/path/to/?key=val``.
|
||||||
|
The cache keys generated by Django 1.7 will be different from the keys
|
||||||
|
generated by older versions of Django. After upgrading to Django 1.7, the first
|
||||||
|
request to any previously cached URL will be a cache miss .
|
||||||
|
|
||||||
Passing ``None`` to ``Manager.db_manager()``
|
Passing ``None`` to ``Manager.db_manager()``
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -1046,13 +1046,19 @@ the contents of a Web page depend on a user's language preference, the page is
|
||||||
said to "vary on language."
|
said to "vary on language."
|
||||||
|
|
||||||
By default, Django's cache system creates its cache keys using the requested
|
By default, Django's cache system creates its cache keys using the requested
|
||||||
path and query -- e.g., ``"/stories/2005/?order_by=author"``. This means every
|
fully-qualified URL -- e.g.,
|
||||||
|
``"http://www.example.com/stories/2005/?order_by=author"``. This means every
|
||||||
request to that URL will use the same cached version, regardless of user-agent
|
request to that URL will use the same cached version, regardless of user-agent
|
||||||
differences such as cookies or language preferences. However, if this page
|
differences such as cookies or language preferences. However, if this page
|
||||||
produces different content based on some difference in request headers -- such
|
produces different content based on some difference in request headers -- such
|
||||||
as a cookie, or a language, or a user-agent -- you'll need to use the ``Vary``
|
as a cookie, or a language, or a user-agent -- you'll need to use the ``Vary``
|
||||||
header to tell caching mechanisms that the page output depends on those things.
|
header to tell caching mechanisms that the page output depends on those things.
|
||||||
|
|
||||||
|
.. versionchanged:: 1.7
|
||||||
|
|
||||||
|
Cache keys use the request's fully-qualified URL rather than path
|
||||||
|
and query string.
|
||||||
|
|
||||||
To do this in Django, use the convenient
|
To do this in Django, use the convenient
|
||||||
:func:`django.views.decorators.vary.vary_on_headers` view decorator, like so::
|
:func:`django.views.decorators.vary.vary_on_headers` view decorator, like so::
|
||||||
|
|
||||||
|
|
|
@ -1198,8 +1198,20 @@ class CacheUtils(TestCase):
|
||||||
"""TestCase for django.utils.cache functions."""
|
"""TestCase for django.utils.cache functions."""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
self.host = 'www.example.com'
|
||||||
self.path = '/cache/test/'
|
self.path = '/cache/test/'
|
||||||
self.factory = RequestFactory()
|
self.factory = RequestFactory(HTTP_HOST=self.host)
|
||||||
|
|
||||||
|
def _get_request_cache(self, method='GET', query_string=None, update_cache=None):
|
||||||
|
request = self._get_request(self.host, self.path,
|
||||||
|
method, query_string=query_string)
|
||||||
|
request._cache_update_cache = True if not update_cache else update_cache
|
||||||
|
return request
|
||||||
|
|
||||||
|
def _set_cache(self, request, msg):
|
||||||
|
response = HttpResponse()
|
||||||
|
response.content = msg
|
||||||
|
return UpdateCacheMiddleware().process_response(request, response)
|
||||||
|
|
||||||
def test_patch_vary_headers(self):
|
def test_patch_vary_headers(self):
|
||||||
headers = (
|
headers = (
|
||||||
|
@ -1229,10 +1241,19 @@ class CacheUtils(TestCase):
|
||||||
self.assertEqual(get_cache_key(request), None)
|
self.assertEqual(get_cache_key(request), None)
|
||||||
# Set headers to an empty list.
|
# Set headers to an empty list.
|
||||||
learn_cache_key(request, response)
|
learn_cache_key(request, response)
|
||||||
self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.GET.9fa0fd092afb73bdce204bb4f94d5804.d41d8cd98f00b204e9800998ecf8427e')
|
|
||||||
|
self.assertEqual(
|
||||||
|
get_cache_key(request),
|
||||||
|
'views.decorators.cache.cache_page.settingsprefix.GET.'
|
||||||
|
'18a03f9c9649f7d684af5db3524f5c99.d41d8cd98f00b204e9800998ecf8427e'
|
||||||
|
)
|
||||||
# Verify that a specified key_prefix is taken into account.
|
# Verify that a specified key_prefix is taken into account.
|
||||||
learn_cache_key(request, response, key_prefix=key_prefix)
|
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.GET.9fa0fd092afb73bdce204bb4f94d5804.d41d8cd98f00b204e9800998ecf8427e')
|
self.assertEqual(
|
||||||
|
get_cache_key(request, key_prefix=key_prefix),
|
||||||
|
'views.decorators.cache.cache_page.localprefix.GET.'
|
||||||
|
'18a03f9c9649f7d684af5db3524f5c99.d41d8cd98f00b204e9800998ecf8427e'
|
||||||
|
)
|
||||||
|
|
||||||
def test_get_cache_key_with_query(self):
|
def test_get_cache_key_with_query(self):
|
||||||
request = self.factory.get(self.path, {'test': 1})
|
request = self.factory.get(self.path, {'test': 1})
|
||||||
|
@ -1242,7 +1263,22 @@ class CacheUtils(TestCase):
|
||||||
# Set headers to an empty list.
|
# Set headers to an empty list.
|
||||||
learn_cache_key(request, response)
|
learn_cache_key(request, response)
|
||||||
# Verify that the querystring is taken into account.
|
# Verify that the querystring is taken into account.
|
||||||
self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.GET.d11198ba31883732b0de5786a80cc12b.d41d8cd98f00b204e9800998ecf8427e')
|
|
||||||
|
self.assertEqual(
|
||||||
|
get_cache_key(request),
|
||||||
|
'views.decorators.cache.cache_page.settingsprefix.GET.'
|
||||||
|
'beaf87a9a99ee81c673ea2d67ccbec2a.d41d8cd98f00b204e9800998ecf8427e'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_cache_key_varies_by_url(self):
|
||||||
|
"""
|
||||||
|
get_cache_key keys differ by fully-qualfied URL instead of path
|
||||||
|
"""
|
||||||
|
request1 = self.factory.get(self.path, HTTP_HOST='sub-1.example.com')
|
||||||
|
learn_cache_key(request1, HttpResponse())
|
||||||
|
request2 = self.factory.get(self.path, HTTP_HOST='sub-2.example.com')
|
||||||
|
learn_cache_key(request2, HttpResponse())
|
||||||
|
self.assertTrue(get_cache_key(request1) != get_cache_key(request2))
|
||||||
|
|
||||||
def test_learn_cache_key(self):
|
def test_learn_cache_key(self):
|
||||||
request = self.factory.head(self.path)
|
request = self.factory.head(self.path)
|
||||||
|
@ -1250,7 +1286,12 @@ class CacheUtils(TestCase):
|
||||||
response['Vary'] = 'Pony'
|
response['Vary'] = 'Pony'
|
||||||
# Make sure that the Vary header is added to the key hash
|
# Make sure that the Vary header is added to the key hash
|
||||||
learn_cache_key(request, response)
|
learn_cache_key(request, response)
|
||||||
self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.GET.9fa0fd092afb73bdce204bb4f94d5804.d41d8cd98f00b204e9800998ecf8427e')
|
|
||||||
|
self.assertEqual(
|
||||||
|
get_cache_key(request),
|
||||||
|
'views.decorators.cache.cache_page.settingsprefix.GET.'
|
||||||
|
'18a03f9c9649f7d684af5db3524f5c99.d41d8cd98f00b204e9800998ecf8427e'
|
||||||
|
)
|
||||||
|
|
||||||
def test_patch_cache_control(self):
|
def test_patch_cache_control(self):
|
||||||
tests = (
|
tests = (
|
||||||
|
@ -1874,10 +1915,19 @@ class TestWithTemplateResponse(TestCase):
|
||||||
self.assertEqual(get_cache_key(request), None)
|
self.assertEqual(get_cache_key(request), None)
|
||||||
# Set headers to an empty list.
|
# Set headers to an empty list.
|
||||||
learn_cache_key(request, response)
|
learn_cache_key(request, response)
|
||||||
self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.GET.9fa0fd092afb73bdce204bb4f94d5804.d41d8cd98f00b204e9800998ecf8427e')
|
|
||||||
|
self.assertEqual(
|
||||||
|
get_cache_key(request),
|
||||||
|
'views.decorators.cache.cache_page.settingsprefix.GET.'
|
||||||
|
'58a0a05c8a5620f813686ff969c26853.d41d8cd98f00b204e9800998ecf8427e'
|
||||||
|
)
|
||||||
# Verify that a specified key_prefix is taken into account.
|
# Verify that a specified key_prefix is taken into account.
|
||||||
learn_cache_key(request, response, key_prefix=key_prefix)
|
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.GET.9fa0fd092afb73bdce204bb4f94d5804.d41d8cd98f00b204e9800998ecf8427e')
|
self.assertEqual(
|
||||||
|
get_cache_key(request, key_prefix=key_prefix),
|
||||||
|
'views.decorators.cache.cache_page.localprefix.GET.'
|
||||||
|
'58a0a05c8a5620f813686ff969c26853.d41d8cd98f00b204e9800998ecf8427e'
|
||||||
|
)
|
||||||
|
|
||||||
def test_get_cache_key_with_query(self):
|
def test_get_cache_key_with_query(self):
|
||||||
request = self.factory.get(self.path, {'test': 1})
|
request = self.factory.get(self.path, {'test': 1})
|
||||||
|
@ -1887,7 +1937,11 @@ class TestWithTemplateResponse(TestCase):
|
||||||
# Set headers to an empty list.
|
# Set headers to an empty list.
|
||||||
learn_cache_key(request, response)
|
learn_cache_key(request, response)
|
||||||
# Verify that the querystring is taken into account.
|
# Verify that the querystring is taken into account.
|
||||||
self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.GET.d11198ba31883732b0de5786a80cc12b.d41d8cd98f00b204e9800998ecf8427e')
|
self.assertEqual(
|
||||||
|
get_cache_key(request),
|
||||||
|
'views.decorators.cache.cache_page.settingsprefix.GET.'
|
||||||
|
'0f1c2d56633c943073c4569d9a9502fe.d41d8cd98f00b204e9800998ecf8427e'
|
||||||
|
)
|
||||||
|
|
||||||
@override_settings(USE_ETAGS=False)
|
@override_settings(USE_ETAGS=False)
|
||||||
def test_without_etag(self):
|
def test_without_etag(self):
|
||||||
|
|
Loading…
Reference in New Issue