Django

Code

root/django/branches/newforms-admin/django/utils/cache.py

Revision 7669, 7.8 kB (checked in by brosner, 6 months ago)

newforms-admin: Merged from trunk up to [7668].

  • Property svn:eol-style set to native
Line 
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 md5
21 import re
22 import time
23 try:
24     set
25 except NameError:
26     from sets import Set as set   # Python 2.3 fallback
27
28 from django.conf import settings
29 from django.core.cache import cache
30 from django.utils.encoding import smart_str, iri_to_uri
31 from django.utils.http import http_date
32
33 cc_delim_re = re.compile(r'\s*,\s*')
34
35 def patch_cache_control(response, **kwargs):
36     """
37     This function patches the Cache-Control header by adding all
38     keyword arguments to it. The transformation is as follows:
39
40     * All keyword parameter names are turned to lowercase, and underscores
41       are converted to hyphens.
42     * If the value of a parameter is True (exactly True, not just a
43       true value), only the parameter name is added to the header.
44     * All other parameters are added with their value, after applying
45       str() to it.
46     """
47     def dictitem(s):
48         t = s.split('=', 1)
49         if len(t) > 1:
50             return (t[0].lower(), t[1])
51         else:
52             return (t[0].lower(), True)
53
54     def dictvalue(t):
55         if t[1] is True:
56             return t[0]
57         else:
58             return t[0] + '=' + smart_str(t[1])
59
60     if response.has_header('Cache-Control'):
61         cc = cc_delim_re.split(response['Cache-Control'])
62         cc = dict([dictitem(el) for el in cc])
63     else:
64         cc = {}
65
66     # If there's already a max-age header but we're being asked to set a new
67     # max-age, use the minumum of the two ages. In practice this happens when
68     # a decorator and a piece of middleware both operate on a given view.
69     if 'max-age' in cc and 'max_age' in kwargs:
70         kwargs['max_age'] = min(cc['max-age'], kwargs['max_age'])
71
72     for (k, v) in kwargs.items():
73         cc[k.replace('_', '-')] = v
74     cc = ', '.join([dictvalue(el) for el in cc.items()])
75     response['Cache-Control'] = cc
76
77 def get_max_age(response):
78     """
79     Returns the max-age from the response Cache-Control header as an integer
80     (or ``None`` if it wasn't found or wasn't an integer.
81     """
82     if not response.has_header('Cache-Control'):
83         return
84     cc = dict([_to_tuple(el) for el in
85         cc_delim_re.split(response['Cache-Control'])])
86     if 'max-age' in cc:
87         try:
88             return int(cc['max-age'])
89         except (ValueError, TypeError):
90             pass
91
92 def patch_response_headers(response, cache_timeout=None):
93     """
94     Adds some useful headers to the given HttpResponse object:
95         ETag, Last-Modified, Expires and Cache-Control
96
97     Each header is only added if it isn't already set.
98
99     cache_timeout is in seconds. The CACHE_MIDDLEWARE_SECONDS setting is used
100     by default.
101     """
102     if cache_timeout is None:
103         cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS
104     if cache_timeout < 0:
105         cache_timeout = 0 # Can't have max-age negative
106     if not response.has_header('ETag'):
107         response['ETag'] = '"%s"' % md5.new(response.content).hexdigest()
108     if not response.has_header('Last-Modified'):
109         response['Last-Modified'] = http_date()
110     if not response.has_header('Expires'):
111         response['Expires'] = http_date(time.time() + cache_timeout)
112     patch_cache_control(response, max_age=cache_timeout)
113
114 def add_never_cache_headers(response):
115     """
116     Adds headers to a response to indicate that a page should never be cached.
117     """
118     patch_response_headers(response, cache_timeout=-1)
119
120 def patch_vary_headers(response, newheaders):
121     """
122     Adds (or updates) the "Vary" header in the given HttpResponse object.
123     newheaders is a list of header names that should be in "Vary". Existing
124     headers in "Vary" aren't removed.
125     """
126     # Note that we need to keep the original order intact, because cache
127     # implementations may rely on the order of the Vary contents in, say,
128     # computing an MD5 hash.
129     if response.has_header('Vary'):
130         vary_headers = cc_delim_re.split(response['Vary'])
131     else:
132         vary_headers = []
133     # Use .lower() here so we treat headers as case-insensitive.
134     existing_headers = set([header.lower() for header in vary_headers])
135     additional_headers = [newheader for newheader in newheaders
136                           if newheader.lower() not in existing_headers]
137     response['Vary'] = ', '.join(vary_headers + additional_headers)
138
139 def _generate_cache_key(request, headerlist, key_prefix):
140     """Returns a cache key from the headers given in the header list."""
141     ctx = md5.new()
142     for header in headerlist:
143         value = request.META.get(header, None)
144         if value is not None:
145             ctx.update(value)
146     return 'views.decorators.cache.cache_page.%s.%s.%s' % (
147                key_prefix, iri_to_uri(request.path), ctx.hexdigest())
148
149 def get_cache_key(request, key_prefix=None):
150     """
151     Returns a cache key based on the request path. It can be used in the
152     request phase because it pulls the list of headers to take into account
153     from the global path registry and uses those to build a cache key to check
154     against.
155
156     If there is no headerlist stored, the page needs to be rebuilt, so this
157     function returns None.
158     """
159     if key_prefix is None:
160         key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
161     cache_key = 'views.decorators.cache.cache_header.%s.%s' % (
162                     key_prefix, iri_to_uri(request.path))
163     headerlist = cache.get(cache_key, None)
164     if headerlist is not None:
165         return _generate_cache_key(request, headerlist, key_prefix)
166     else:
167         return None
168
169 def learn_cache_key(request, response, cache_timeout=None, key_prefix=None):
170     """
171     Learns what headers to take into account for some request path from the
172     response object. It stores those headers in a global path registry so that
173     later access to that path will know what headers to take into account
174     without building the response object itself. The headers are named in the
175     Vary header of the response, but we want to prevent response generation.
176
177     The list of headers to use for cache key generation is stored in the same
178     cache as the pages themselves. If the cache ages some data out of the
179     cache, this just means that we have to build the response once to get at
180     the Vary header and so at the list of headers to use for the cache key.
181     """
182     if key_prefix is None:
183         key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
184     if cache_timeout is None:
185         cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS
186     cache_key = 'views.decorators.cache.cache_header.%s.%s' % (
187                     key_prefix, iri_to_uri(request.path))
188     if response.has_header('Vary'):
189         headerlist = ['HTTP_'+header.upper().replace('-', '_')
190                       for header in cc_delim_re.split(response['Vary'])]
191         cache.set(cache_key, headerlist, cache_timeout)
192         return _generate_cache_key(request, headerlist, key_prefix)
193     else:
194         # if there is no Vary header, we still need a cache key
195         # for the request.path
196         cache.set(cache_key, [], cache_timeout)
197         return _generate_cache_key(request, [], key_prefix)
198
199
200 def _to_tuple(s):
201     t = s.split('=',1)
202     if len(t) == 2:
203         return t[0].lower(), t[1]
204     return t[0].lower(), True
Note: See TracBrowser for help on using the browser.