app/django/utils/cache.py
changeset 54 03e267d67478
child 323 ff1a9aa48cfd
equal deleted inserted replaced
53:57b4279d8c4e 54:03e267d67478
       
     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'] = 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