root / env / lib / python2.7 / site-packages / django / utils / cache.py @ 1a305335
History | View | Annotate | Download (10.2 KB)
1 |
"""
|
---|---|
2 |
This module contains helper functions for controlling caching. It does so by
|
3 |
managing the "Vary" header of responses. It includes functions to patch the
|
4 |
header of response objects directly and decorators that change functions to do
|
5 |
that header-patching themselves.
|
6 |
|
7 |
For information on the Vary header, see:
|
8 |
|
9 |
http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.44
|
10 |
|
11 |
Essentially, the "Vary" HTTP header defines which headers a cache should take
|
12 |
into account when building its cache key. Requests with the same path but
|
13 |
different header content for headers named in "Vary" need to get different
|
14 |
cache keys to prevent delivery of wrong content.
|
15 |
|
16 |
An example: i18n middleware would need to distinguish caches by the
|
17 |
"Accept-language" header.
|
18 |
"""
|
19 |
|
20 |
import hashlib |
21 |
import re |
22 |
import time |
23 |
|
24 |
from django.conf import settings |
25 |
from django.core.cache import get_cache |
26 |
from django.utils.encoding import smart_str, iri_to_uri, force_unicode |
27 |
from django.utils.http import http_date |
28 |
from django.utils.timezone import get_current_timezone_name |
29 |
from django.utils.translation import get_language |
30 |
|
31 |
cc_delim_re = re.compile(r'\s*,\s*')
|
32 |
|
33 |
def patch_cache_control(response, **kwargs): |
34 |
"""
|
35 |
This function patches the Cache-Control header by adding all
|
36 |
keyword arguments to it. The transformation is as follows:
|
37 |
|
38 |
* All keyword parameter names are turned to lowercase, and underscores
|
39 |
are converted to hyphens.
|
40 |
* If the value of a parameter is True (exactly True, not just a
|
41 |
true value), only the parameter name is added to the header.
|
42 |
* All other parameters are added with their value, after applying
|
43 |
str() to it.
|
44 |
"""
|
45 |
def dictitem(s): |
46 |
t = s.split('=', 1) |
47 |
if len(t) > 1: |
48 |
return (t[0].lower(), t[1]) |
49 |
else:
|
50 |
return (t[0].lower(), True) |
51 |
|
52 |
def dictvalue(t): |
53 |
if t[1] is True: |
54 |
return t[0] |
55 |
else:
|
56 |
return t[0] + '=' + smart_str(t[1]) |
57 |
|
58 |
if response.has_header('Cache-Control'): |
59 |
cc = cc_delim_re.split(response['Cache-Control'])
|
60 |
cc = dict([dictitem(el) for el in cc]) |
61 |
else:
|
62 |
cc = {} |
63 |
|
64 |
# If there's already a max-age header but we're being asked to set a new
|
65 |
# max-age, use the minimum of the two ages. In practice this happens when
|
66 |
# a decorator and a piece of middleware both operate on a given view.
|
67 |
if 'max-age' in cc and 'max_age' in kwargs: |
68 |
kwargs['max_age'] = min(cc['max-age'], kwargs['max_age']) |
69 |
|
70 |
# Allow overriding private caching and vice versa
|
71 |
if 'private' in cc and 'public' in kwargs: |
72 |
del cc['private'] |
73 |
elif 'public' in cc and 'private' in kwargs: |
74 |
del cc['public'] |
75 |
|
76 |
for (k, v) in kwargs.items(): |
77 |
cc[k.replace('_', '-')] = v |
78 |
cc = ', '.join([dictvalue(el) for el in cc.items()]) |
79 |
response['Cache-Control'] = cc
|
80 |
|
81 |
def get_max_age(response): |
82 |
"""
|
83 |
Returns the max-age from the response Cache-Control header as an integer
|
84 |
(or ``None`` if it wasn't found or wasn't an integer.
|
85 |
"""
|
86 |
if not response.has_header('Cache-Control'): |
87 |
return
|
88 |
cc = dict([_to_tuple(el) for el in |
89 |
cc_delim_re.split(response['Cache-Control'])])
|
90 |
if 'max-age' in cc: |
91 |
try:
|
92 |
return int(cc['max-age']) |
93 |
except (ValueError, TypeError): |
94 |
pass
|
95 |
|
96 |
def _set_response_etag(response): |
97 |
response['ETag'] = '"%s"' % hashlib.md5(response.content).hexdigest() |
98 |
return response
|
99 |
|
100 |
def patch_response_headers(response, cache_timeout=None): |
101 |
"""
|
102 |
Adds some useful headers to the given HttpResponse object:
|
103 |
ETag, Last-Modified, Expires and Cache-Control
|
104 |
|
105 |
Each header is only added if it isn't already set.
|
106 |
|
107 |
cache_timeout is in seconds. The CACHE_MIDDLEWARE_SECONDS setting is used
|
108 |
by default.
|
109 |
"""
|
110 |
if cache_timeout is None: |
111 |
cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS |
112 |
if cache_timeout < 0: |
113 |
cache_timeout = 0 # Can't have max-age negative |
114 |
if settings.USE_ETAGS and not response.has_header('ETag'): |
115 |
if hasattr(response, 'render') and callable(response.render): |
116 |
response.add_post_render_callback(_set_response_etag) |
117 |
else:
|
118 |
response = _set_response_etag(response) |
119 |
if not response.has_header('Last-Modified'): |
120 |
response['Last-Modified'] = http_date()
|
121 |
if not response.has_header('Expires'): |
122 |
response['Expires'] = http_date(time.time() + cache_timeout)
|
123 |
patch_cache_control(response, max_age=cache_timeout) |
124 |
|
125 |
def add_never_cache_headers(response): |
126 |
"""
|
127 |
Adds headers to a response to indicate that a page should never be cached.
|
128 |
"""
|
129 |
patch_response_headers(response, cache_timeout=-1)
|
130 |
|
131 |
def patch_vary_headers(response, newheaders): |
132 |
"""
|
133 |
Adds (or updates) the "Vary" header in the given HttpResponse object.
|
134 |
newheaders is a list of header names that should be in "Vary". Existing
|
135 |
headers in "Vary" aren't removed.
|
136 |
"""
|
137 |
# Note that we need to keep the original order intact, because cache
|
138 |
# implementations may rely on the order of the Vary contents in, say,
|
139 |
# computing an MD5 hash.
|
140 |
if response.has_header('Vary'): |
141 |
vary_headers = cc_delim_re.split(response['Vary'])
|
142 |
else:
|
143 |
vary_headers = [] |
144 |
# Use .lower() here so we treat headers as case-insensitive.
|
145 |
existing_headers = set([header.lower() for header in vary_headers]) |
146 |
additional_headers = [newheader for newheader in newheaders |
147 |
if newheader.lower() not in existing_headers] |
148 |
response['Vary'] = ', '.join(vary_headers + additional_headers) |
149 |
|
150 |
def has_vary_header(response, header_query): |
151 |
"""
|
152 |
Checks to see if the response has a given header name in its Vary header.
|
153 |
"""
|
154 |
if not response.has_header('Vary'): |
155 |
return False |
156 |
vary_headers = cc_delim_re.split(response['Vary'])
|
157 |
existing_headers = set([header.lower() for header in vary_headers]) |
158 |
return header_query.lower() in existing_headers |
159 |
|
160 |
def _i18n_cache_key_suffix(request, cache_key): |
161 |
"""If necessary, adds the current locale or time zone to the cache key."""
|
162 |
if settings.USE_I18N or settings.USE_L10N: |
163 |
# first check if LocaleMiddleware or another middleware added
|
164 |
# LANGUAGE_CODE to request, then fall back to the active language
|
165 |
# which in turn can also fall back to settings.LANGUAGE_CODE
|
166 |
cache_key += '.%s' % getattr(request, 'LANGUAGE_CODE', get_language()) |
167 |
if settings.USE_TZ:
|
168 |
# The datetime module doesn't restrict the output of tzname().
|
169 |
# Windows is known to use non-standard, locale-dependant names.
|
170 |
# User-defined tzinfo classes may return absolutely anything.
|
171 |
# Hence this paranoid conversion to create a valid cache key.
|
172 |
tz_name = force_unicode(get_current_timezone_name(), errors='ignore')
|
173 |
cache_key += '.%s' % tz_name.encode('ascii', 'ignore').replace(' ', '_') |
174 |
return cache_key
|
175 |
|
176 |
def _generate_cache_key(request, method, headerlist, key_prefix): |
177 |
"""Returns a cache key from the headers given in the header list."""
|
178 |
ctx = hashlib.md5() |
179 |
for header in headerlist: |
180 |
value = request.META.get(header, None)
|
181 |
if value is not None: |
182 |
ctx.update(value) |
183 |
path = hashlib.md5(iri_to_uri(request.get_full_path())) |
184 |
cache_key = 'views.decorators.cache.cache_page.%s.%s.%s.%s' % (
|
185 |
key_prefix, method, path.hexdigest(), ctx.hexdigest()) |
186 |
return _i18n_cache_key_suffix(request, cache_key)
|
187 |
|
188 |
def _generate_cache_header_key(key_prefix, request): |
189 |
"""Returns a cache key for the header cache."""
|
190 |
path = hashlib.md5(iri_to_uri(request.get_full_path())) |
191 |
cache_key = 'views.decorators.cache.cache_header.%s.%s' % (
|
192 |
key_prefix, path.hexdigest()) |
193 |
return _i18n_cache_key_suffix(request, cache_key)
|
194 |
|
195 |
def get_cache_key(request, key_prefix=None, method='GET', cache=None): |
196 |
"""
|
197 |
Returns a cache key based on the request path and query. It can be used
|
198 |
in the request phase because it pulls the list of headers to take into
|
199 |
account from the global path registry and uses those to build a cache key
|
200 |
to check against.
|
201 |
|
202 |
If there is no headerlist stored, the page needs to be rebuilt, so this
|
203 |
function returns None.
|
204 |
"""
|
205 |
if key_prefix is None: |
206 |
key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX |
207 |
cache_key = _generate_cache_header_key(key_prefix, request) |
208 |
if cache is None: |
209 |
cache = get_cache(settings.CACHE_MIDDLEWARE_ALIAS) |
210 |
headerlist = cache.get(cache_key, None)
|
211 |
if headerlist is not None: |
212 |
return _generate_cache_key(request, method, headerlist, key_prefix)
|
213 |
else:
|
214 |
return None |
215 |
|
216 |
def learn_cache_key(request, response, cache_timeout=None, key_prefix=None, cache=None): |
217 |
"""
|
218 |
Learns what headers to take into account for some request path from the
|
219 |
response object. It stores those headers in a global path registry so that
|
220 |
later access to that path will know what headers to take into account
|
221 |
without building the response object itself. The headers are named in the
|
222 |
Vary header of the response, but we want to prevent response generation.
|
223 |
|
224 |
The list of headers to use for cache key generation is stored in the same
|
225 |
cache as the pages themselves. If the cache ages some data out of the
|
226 |
cache, this just means that we have to build the response once to get at
|
227 |
the Vary header and so at the list of headers to use for the cache key.
|
228 |
"""
|
229 |
if key_prefix is None: |
230 |
key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX |
231 |
if cache_timeout is None: |
232 |
cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS |
233 |
cache_key = _generate_cache_header_key(key_prefix, request) |
234 |
if cache is None: |
235 |
cache = get_cache(settings.CACHE_MIDDLEWARE_ALIAS) |
236 |
if response.has_header('Vary'): |
237 |
headerlist = ['HTTP_'+header.upper().replace('-', '_') |
238 |
for header in cc_delim_re.split(response['Vary'])] |
239 |
cache.set(cache_key, headerlist, cache_timeout) |
240 |
return _generate_cache_key(request, request.method, headerlist, key_prefix)
|
241 |
else:
|
242 |
# if there is no Vary header, we still need a cache key
|
243 |
# for the request.get_full_path()
|
244 |
cache.set(cache_key, [], cache_timeout) |
245 |
return _generate_cache_key(request, request.method, [], key_prefix)
|
246 |
|
247 |
|
248 |
def _to_tuple(s): |
249 |
t = s.split('=',1) |
250 |
if len(t) == 2: |
251 |
return t[0].lower(), t[1] |
252 |
return t[0].lower(), True |