/bangkokhotel/lib/python2.5/site-packages/django/utils/cache.py
Python | 252 lines | 250 code | 0 blank | 2 comment | 0 complexity | d4c8f2681ffdebdc93f1b8428a00379e MD5 | raw file
1"""
2This module contains helper functions for controlling caching. It does so by
3managing the "Vary" header of responses. It includes functions to patch the
4header of response objects directly and decorators that change functions to do
5that header-patching themselves.
6
7For information on the Vary header, see:
8
9 http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.44
10
11Essentially, the "Vary" HTTP header defines which headers a cache should take
12into account when building its cache key. Requests with the same path but
13different header content for headers named in "Vary" need to get different
14cache keys to prevent delivery of wrong content.
15
16An example: i18n middleware would need to distinguish caches by the
17"Accept-language" header.
18"""
19
20import hashlib
21import re
22import time
23
24from django.conf import settings
25from django.core.cache import get_cache
26from django.utils.encoding import smart_str, iri_to_uri, force_unicode
27from django.utils.http import http_date
28from django.utils.timezone import get_current_timezone_name
29from django.utils.translation import get_language
30
31cc_delim_re = re.compile(r'\s*,\s*')
32
33def 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
81def 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
96def _set_response_etag(response):
97 response['ETag'] = '"%s"' % hashlib.md5(response.content).hexdigest()
98 return response
99
100def 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
125def 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
131def 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
150def 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
160def _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
176def _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
188def _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
195def 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
216def 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
248def _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