Project

General

Profile

Statistics
| Branch: | Revision:

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