How to Generate cache_key of Django Page Cache

  • 2021-09-24 23:03:12
  • OfStack

Page caching

e.g.


@cache_page(time_out, key_prefix=key_prefix)
def my_view():
 ...

By default, default cache in the configuration is used

The cache_page decorator is converted from the caching middleware CacheMiddleware

CacheMiddleware inherits UpdateCacheMiddleware and FetchFromCacheMiddleware

UpdateCacheMiddleware inherits from MiddlewareMixin and only overrides the process_response method to cache views after they have been processed


class UpdateCacheMiddleware(MiddlewareMixin):
 def process_response(self, request, response):
  """Sets the cache, if needed."""
  ...
  if timeout and response.status_code == 200:
   #  According to the request and response parameters, the set key_prefix That generates the page cache key
   cache_key = learn_cache_key(request, response, timeout, self.key_prefix, cache=self.cache)
   self.cache.set(cache_key, response, timeout)
  return response

FetchFromCacheMiddleware inherits from MiddlewareMixin and only overrides the process_request method to get the cache of the current view


# django/middleware/cache.py
class FetchFromCacheMiddleware(MiddlewareMixin):
 def process_request(self, request):
  """
  Checks whether the page is already cached and returns the cached
  version if available.
  """
  #  Only for methods that are  GET  Or  HEAD  Get the cache from the request of 
  if request.method not in ('GET', 'HEAD'):
   request._cache_update_cache = False
   return None # Don't bother checking the cache.

  # try and get the cached GET response
  #  Here, according to the requested information, the cache key prefix is generated 1 A cache_key . By default, access to the same 1 Interface its cache_key Should be the same 
  cache_key = get_cache_key(request, self.key_prefix, 'GET', cache=self.cache)
  if cache_key is None:
   request._cache_update_cache = True
   return None # No cache information available, need to rebuild.
  #  If you get the response Is returned directly to the cached response The actual view will not be executed 
  response = self.cache.get(cache_key)
  # if it wasn't found and we are looking for a HEAD, try looking just for that
  if response is None and request.method == 'HEAD':
   cache_key = get_cache_key(request, self.key_prefix, 'HEAD', cache=self.cache)
   response = self.cache.get(cache_key)

  if response is None:
   #  If the cache is not obtained, it will return None Is executed to the actual view and the cache is rebuilt 
   request._cache_update_cache = True
   return None # No cache information available, need to rebuild.

  # hit, return cached response
  request._cache_update_cache = False
  return response

cache_key for page caching

This section will answer two questions:

Why does one page save two key in redis: cache_key and cache_header? How is page cache identified by only one? Will the same cache be used when the request header is different (for example, if another user requests the same page)?

Let's start with learn_cache_key in the process of saving the cached view


# django/utils/cache.py
def learn_cache_key(request, response, cache_timeout=None, key_prefix=None, cache=None):
 # 见下文,这个cache_key由 request的完整url 以及 key_prefix 唯1确定
 cache_key = _generate_cache_header_key(key_prefix, request)
 if cache is None:
  # cache 是1个缓存实例
  cache = caches[settings.CACHE_MIDDLEWARE_ALIAS]
 # Vary 是1个HTTP响应头字段。其内容是1个或多个http头部名称
 # 比如 `Vary: User-Agent` 表示此响应根据请求头 `User-Agent` 的值有所不同
 # 只有当下1个请求的 `User-Agent` 值与当前请求相同时,才会使用当前响应的缓存
 if response.has_header('Vary'):
  headerlist = []
  for header in cc_delim_re.split(response['Vary']):
   # 将 Vary 中出现的 http头部名称 加到 headerlist 中去
   header = header.upper().replace('-', '_')
   headerlist.append('HTTP_' + header)
  headerlist.sort()
  # 当前 cache_key 实际上是 cache_header_key,它存的是响应头中Vary字段的值
  cache.set(cache_key, headerlist, cache_timeout)
  # 这里返回的才是页面内容对应的 cache_key,它由 
  # 出现在Vary字段中的request请求头字段的值(有序拼在1起)、request的完整url、request的method、key_prefix 唯1确定
  return _generate_cache_key(request, request.method, headerlist, key_prefix)
 else:
  # if there is no Vary header, we still need a cache key
  # for the request.build_absolute_uri()
  cache.set(cache_key, [], cache_timeout)
  return _generate_cache_key(request, request.method, [], key_prefix)

def _generate_cache_header_key(key_prefix, request):
 """Returns a cache key for the header cache."""
 # request.build_absolute_uri()返回的是完整的请求URL。如 http://127.0.0.1:8000/api/leaflet/filterList?a=1
 # 因此,请求同1个接口,但是接口参数不同,会生成两个cache_key
 url = hashlib.md5(force_bytes(iri_to_uri(request.build_absolute_uri())))
 cache_key = 'views.decorators.cache.cache_header.%s.%s' % (
  key_prefix, url.hexdigest())
 return _i18n_cache_key_suffix(request, cache_key)

def _generate_cache_key(request, method, headerlist, key_prefix):
 """Returns a cache key from the headers given in the header list."""
 ctx = hashlib.md5()
 # headerlist是响应头中Vary字段的值
 for header in headerlist:
  # 出现在Vary字段中的request请求头字段的值
  value = request.META.get(header)
  if value is not None:
   ctx.update(force_bytes(value))
 url = hashlib.md5(force_bytes(iri_to_uri(request.build_absolute_uri())))
 cache_key = 'views.decorators.cache.cache_page.%s.%s.%s.%s' % (
  key_prefix, method, url.hexdigest(), ctx.hexdigest())
 return _i18n_cache_key_suffix(request, cache_key)
​ 再看获取缓存的get_cache_key方法

def get_cache_key(request, key_prefix=None, method='GET', cache=None):
 # 由 request的完整url 以及 key_prefix 生成 cache_header_key
 cache_key = _generate_cache_header_key(key_prefix, request)
 # headerlist是之前缓存的 与当前请求具有相同cache_header_key 的请求的响应的响应头中Vary字段的值
 headerlist = cache.get(cache_key)
 # 即使响应头没有Vary字段,还是会针对当前 cache_header_key 存1个空数组
 # 因此如果headerlist为None,表示当前请求没有缓存
 if headerlist is not None:
  # 根据 出现在Vary字段中的request请求头字段的值(有序拼在1起)、request的完整url、request的method、key_prefix 生成 cache_key
  return _generate_cache_key(request, method, headerlist, key_prefix)
 else:
  return None

To sum up:

cache_header stores the value of the response header Vary field, and cache_key stores the cache view cache_key is determined by the value of the request request header field that appears in the Vary field (ordered from 1), the complete url of request, the method of request, and the key_prefix only 1 When the request header is different, it is possible to use the same cache, depending on whether a different request header field name appears in the Vary field of the response header. For example, if there is Vary: User-Agent in the response header, two requests with different User-Agent will necessarily generate different cache_key, so the same cache will not be used. But if you just add an cache-control: no-cache (browser-provided Disable cache) to the request header and access the same url, you will still hit the previous cache

Related articles: