web/httpcache.py
changeset 0 b97547f5f1fa
child 762 a6f678fe7e44
equal deleted inserted replaced
-1:000000000000 0:b97547f5f1fa
       
     1 """HTTP cache managers
       
     2 
       
     3 
       
     4 :organization: Logilab
       
     5 :copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
       
     6 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
     7 """
       
     8 __docformat__ = "restructuredtext en"
       
     9 
       
    10 from mx.DateTime import DateTimeFromTicks, now, gmtime
       
    11 
       
    12 # time delta usable to convert localized time to GMT time
       
    13 GMTOFFSET = - (now() - gmtime())
       
    14 
       
    15 class NoHTTPCacheManager(object):
       
    16     """default cache manager: set no-cache cache control policy"""
       
    17     def __init__(self, view):
       
    18         self.view = view
       
    19         self.req = view.req
       
    20         self.rset = view.rset
       
    21 
       
    22     def set_headers(self):
       
    23         self.req.set_header('Cache-control', 'no-cache')
       
    24 
       
    25 class MaxAgeHTTPCacheManager(NoHTTPCacheManager):
       
    26     """max-age cache manager: set max-age cache control policy, with max-age
       
    27     specified with the `cache_max_age` attribute of the view
       
    28     """
       
    29     def set_headers(self):
       
    30         self.req.set_header('Cache-control',
       
    31                             'max-age=%s' % self.view.cache_max_age)
       
    32 
       
    33 class EtagHTTPCacheManager(NoHTTPCacheManager):
       
    34     """etag based cache manager for startup views
       
    35 
       
    36     * etag is generated using the view name and the user's groups
       
    37     * set policy to 'must-revalidate' and expires to the current time to force
       
    38       revalidation on each request
       
    39     """
       
    40     # GMT time required
       
    41     date_format = "%a, %d %b %Y %H:%M:%S GMT"
       
    42 
       
    43     def etag(self):
       
    44         return self.view.id + '/' + ','.join(sorted(self.req.user.groups))
       
    45     
       
    46     def max_age(self):
       
    47         # 0 to actually force revalidation
       
    48         return 0
       
    49     
       
    50     def last_modified(self):
       
    51         return self.view.last_modified()
       
    52     
       
    53     def set_headers(self):
       
    54         req = self.req
       
    55         try:
       
    56             req.set_header('Etag', '"%s"' % self.etag())
       
    57         except NoEtag:
       
    58             self.req.set_header('Cache-control', 'no-cache')
       
    59             return
       
    60         req.set_header('Cache-control',
       
    61                        'must-revalidate;max-age=%s' % self.max_age())
       
    62         mdate = self.last_modified()
       
    63         req.set_header('Last-modified', mdate.strftime(self.date_format))
       
    64 
       
    65 class EntityHTTPCacheManager(EtagHTTPCacheManager):
       
    66     """etag based cache manager for view displaying a single entity
       
    67 
       
    68     * etag is generated using entity's eid, the view name and the user's groups
       
    69     * get last modified time from the entity definition (this may not be the
       
    70       entity's modification time since a view may include some related entities
       
    71       with a modification time to consider) using the `last_modified` method
       
    72     """
       
    73     def etag(self):
       
    74         if self.rset is None or len(self.rset) == 0: # entity startup view for instance
       
    75             return super(EntityHTTPCacheManager, self).etag()
       
    76         if len(self.rset) > 1:
       
    77             raise NoEtag()
       
    78         etag = super(EntityHTTPCacheManager, self).etag()
       
    79         eid = self.rset[0][0]
       
    80         if self.req.user.owns(eid):
       
    81             etag += ',owners'
       
    82         return str(eid) + '/' + etag
       
    83 
       
    84 
       
    85 class NoEtag(Exception):
       
    86     """an etag can't be generated"""
       
    87 
       
    88 __all__ = ('GMTOFFSET',
       
    89            'NoHTTPCacheManager', 'MaxAgeHTTPCacheManager',
       
    90            'EtagHTTPCacheManager', 'EntityHTTPCacheManager')
       
    91 
       
    92 # monkey patching, so view doesn't depends on this module and we have all
       
    93 # http cache related logic here
       
    94 
       
    95 from cubicweb.common import view
       
    96 
       
    97 def set_http_cache_headers(self):
       
    98     self.http_cache_manager(self).set_headers()
       
    99 view.View.set_http_cache_headers = set_http_cache_headers
       
   100 
       
   101 def last_modified(self):
       
   102     """return the date/time where this view should be considered as
       
   103     modified. Take care of possible related objects modifications.
       
   104 
       
   105     /!\ must return GMT time /!\
       
   106     """
       
   107     # XXX check view module's file modification time in dev mod ?
       
   108     ctime = gmtime()
       
   109     if self.cache_max_age:
       
   110         mtime = self.req.header_if_modified_since()
       
   111         if mtime:
       
   112             if (ctime - mtime).seconds > self.cache_max_age:
       
   113                 mtime = ctime
       
   114         else:
       
   115             mtime = ctime
       
   116     else:
       
   117         mtime = ctime
       
   118     # mtime = ctime will force page rerendering
       
   119     return mtime
       
   120 view.View.last_modified = last_modified
       
   121 
       
   122 # configure default caching
       
   123 view.View.http_cache_manager = NoHTTPCacheManager
       
   124 # max-age=0 to actually force revalidation when needed
       
   125 view.View.cache_max_age = 0
       
   126 
       
   127 
       
   128 view.EntityView.http_cache_manager = EntityHTTPCacheManager
       
   129 
       
   130 view.StartupView.http_cache_manager = MaxAgeHTTPCacheManager
       
   131 view.StartupView.cache_max_age = 60*60*2 # stay in http cache for 2 hours by default