13
13
different header content for headers named in "Vary" need to get different
14
14
cache keys to prevent delivery of wrong content.
16
A example: i18n middleware would need to distinguish caches by the
16
An example: i18n middleware would need to distinguish caches by the
17
17
"Accept-language" header.
20
import datetime, md5, re
25
from sets import Set as set # Python 2.3 fallback
21
27
from django.conf import settings
22
28
from django.core.cache import cache
29
from django.utils.encoding import smart_str, iri_to_uri
30
from django.utils.http import http_date
31
from django.utils.hashcompat import md5_constructor
24
33
cc_delim_re = re.compile(r'\s*,\s*')
41
return (t[0].lower().replace('-', '_'), t[1])
50
return (t[0].lower(), t[1])
43
return (t[0].lower().replace('-', '_'), True)
52
return (t[0].lower(), True)
49
return t[0] + '=' + str(t[1])
58
return t[0] + '=' + smart_str(t[1])
51
60
if response.has_header('Cache-Control'):
52
61
cc = cc_delim_re.split(response['Cache-Control'])
53
62
cc = dict([dictitem(el) for el in cc])
56
for (k,v) in kwargs.items():
66
# If there's already a max-age header but we're being asked to set a new
67
# max-age, use the minimum 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'])
72
for (k, v) in kwargs.items():
57
73
cc[k.replace('_', '-')] = v
58
74
cc = ', '.join([dictvalue(el) for el in cc.items()])
59
75
response['Cache-Control'] = cc
61
vary_delim_re = re.compile(r',\s*')
77
def get_max_age(response):
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.
82
if not response.has_header('Cache-Control'):
84
cc = dict([_to_tuple(el) for el in
85
cc_delim_re.split(response['Cache-Control'])])
88
return int(cc['max-age'])
89
except (ValueError, TypeError):
63
92
def patch_response_headers(response, cache_timeout=None):
73
102
if cache_timeout is None:
74
103
cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS
75
now = datetime.datetime.utcnow()
104
if cache_timeout < 0:
105
cache_timeout = 0 # Can't have max-age negative
76
106
if not response.has_header('ETag'):
77
response['ETag'] = md5.new(response.content).hexdigest()
107
response['ETag'] = '"%s"' % md5_constructor(response.content).hexdigest()
78
108
if not response.has_header('Last-Modified'):
79
response['Last-Modified'] = now.strftime('%a, %d %b %Y %H:%M:%S GMT')
109
response['Last-Modified'] = http_date()
80
110
if not response.has_header('Expires'):
81
expires = now + datetime.timedelta(0, cache_timeout)
82
response['Expires'] = expires.strftime('%a, %d %b %Y %H:%M:%S GMT')
84
cache_timeout = 0 # Can't have max-age negative
111
response['Expires'] = http_date(time.time() + cache_timeout)
85
112
patch_cache_control(response, max_age=cache_timeout)
87
114
def add_never_cache_headers(response):
89
Add headers to a response to indicate that
90
a page should never be cached.
116
Adds headers to a response to indicate that a page should never be cached.
92
118
patch_response_headers(response, cache_timeout=-1)
100
126
# Note that we need to keep the original order intact, because cache
101
127
# implementations may rely on the order of the Vary contents in, say,
102
128
# computing an MD5 hash.
104
129
if response.has_header('Vary'):
105
vary = vary_delim_re.split(response['Vary'])
106
oldheaders = dict([(el.lower(), 1) for el in vary])
107
for newheader in newheaders:
108
if not newheader.lower() in oldheaders:
109
vary.append(newheader)
110
response['Vary'] = ', '.join(vary)
130
vary_headers = cc_delim_re.split(response['Vary'])
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)
112
139
def _generate_cache_key(request, headerlist, key_prefix):
113
"Returns a cache key from the headers given in the header list."
140
"""Returns a cache key from the headers given in the header list."""
141
ctx = md5_constructor()
115
142
for header in headerlist:
116
143
value = request.META.get(header, None)
117
144
if value is not None:
118
145
ctx.update(value)
119
return 'views.decorators.cache.cache_page.%s.%s.%s' % (key_prefix, request.path, ctx.hexdigest())
146
return 'views.decorators.cache.cache_page.%s.%s.%s' % (
147
key_prefix, iri_to_uri(request.path), ctx.hexdigest())
121
149
def get_cache_key(request, key_prefix=None):
131
159
if key_prefix is None:
132
160
key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
133
cache_key = 'views.decorators.cache.cache_header.%s.%s' % (key_prefix, request.path)
161
cache_key = 'views.decorators.cache.cache_header.%s.%s' % (
162
key_prefix, iri_to_uri(request.path))
134
163
headerlist = cache.get(cache_key, None)
135
164
if headerlist is not None:
136
165
return _generate_cache_key(request, headerlist, key_prefix)
154
183
key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
155
184
if cache_timeout is None:
156
185
cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS
157
cache_key = 'views.decorators.cache.cache_header.%s.%s' % (key_prefix, request.path)
186
cache_key = 'views.decorators.cache.cache_header.%s.%s' % (
187
key_prefix, iri_to_uri(request.path))
158
188
if response.has_header('Vary'):
159
headerlist = ['HTTP_'+header.upper().replace('-', '_') for header in vary_delim_re.split(response['Vary'])]
189
headerlist = ['HTTP_'+header.upper().replace('-', '_')
190
for header in cc_delim_re.split(response['Vary'])]
160
191
cache.set(cache_key, headerlist, cache_timeout)
161
192
return _generate_cache_key(request, headerlist, key_prefix)
164
195
# for the request.path
165
196
cache.set(cache_key, [], cache_timeout)
166
197
return _generate_cache_key(request, [], key_prefix)
203
return t[0].lower(), t[1]
204
return t[0].lower(), True