author | Adrien Di Mascio <Adrien.DiMascio@logilab.fr> |
Mon, 10 Nov 2008 19:33:55 +0100 | |
changeset 16 | a70ece4d9d1a |
parent 0 | b97547f5f1fa |
child 762 | a6f678fe7e44 |
permissions | -rw-r--r-- |
"""HTTP cache managers :organization: Logilab :copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved. :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr """ __docformat__ = "restructuredtext en" from mx.DateTime import DateTimeFromTicks, now, gmtime # time delta usable to convert localized time to GMT time GMTOFFSET = - (now() - gmtime()) class NoHTTPCacheManager(object): """default cache manager: set no-cache cache control policy""" def __init__(self, view): self.view = view self.req = view.req self.rset = view.rset def set_headers(self): self.req.set_header('Cache-control', 'no-cache') class MaxAgeHTTPCacheManager(NoHTTPCacheManager): """max-age cache manager: set max-age cache control policy, with max-age specified with the `cache_max_age` attribute of the view """ def set_headers(self): self.req.set_header('Cache-control', 'max-age=%s' % self.view.cache_max_age) class EtagHTTPCacheManager(NoHTTPCacheManager): """etag based cache manager for startup views * etag is generated using the view name and the user's groups * set policy to 'must-revalidate' and expires to the current time to force revalidation on each request """ # GMT time required date_format = "%a, %d %b %Y %H:%M:%S GMT" def etag(self): return self.view.id + '/' + ','.join(sorted(self.req.user.groups)) def max_age(self): # 0 to actually force revalidation return 0 def last_modified(self): return self.view.last_modified() def set_headers(self): req = self.req try: req.set_header('Etag', '"%s"' % self.etag()) except NoEtag: self.req.set_header('Cache-control', 'no-cache') return req.set_header('Cache-control', 'must-revalidate;max-age=%s' % self.max_age()) mdate = self.last_modified() req.set_header('Last-modified', mdate.strftime(self.date_format)) class EntityHTTPCacheManager(EtagHTTPCacheManager): """etag based cache manager for view displaying a single entity * etag is generated using entity's eid, the view name and the user's groups * get last modified time from the entity definition (this may not be the entity's modification time since a view may include some related entities with a modification time to consider) using the `last_modified` method """ def etag(self): if self.rset is None or len(self.rset) == 0: # entity startup view for instance return super(EntityHTTPCacheManager, self).etag() if len(self.rset) > 1: raise NoEtag() etag = super(EntityHTTPCacheManager, self).etag() eid = self.rset[0][0] if self.req.user.owns(eid): etag += ',owners' return str(eid) + '/' + etag class NoEtag(Exception): """an etag can't be generated""" __all__ = ('GMTOFFSET', 'NoHTTPCacheManager', 'MaxAgeHTTPCacheManager', 'EtagHTTPCacheManager', 'EntityHTTPCacheManager') # monkey patching, so view doesn't depends on this module and we have all # http cache related logic here from cubicweb.common import view def set_http_cache_headers(self): self.http_cache_manager(self).set_headers() view.View.set_http_cache_headers = set_http_cache_headers def last_modified(self): """return the date/time where this view should be considered as modified. Take care of possible related objects modifications. /!\ must return GMT time /!\ """ # XXX check view module's file modification time in dev mod ? ctime = gmtime() if self.cache_max_age: mtime = self.req.header_if_modified_since() if mtime: if (ctime - mtime).seconds > self.cache_max_age: mtime = ctime else: mtime = ctime else: mtime = ctime # mtime = ctime will force page rerendering return mtime view.View.last_modified = last_modified # configure default caching view.View.http_cache_manager = NoHTTPCacheManager # max-age=0 to actually force revalidation when needed view.View.cache_max_age = 0 view.EntityView.http_cache_manager = EntityHTTPCacheManager view.StartupView.http_cache_manager = MaxAgeHTTPCacheManager view.StartupView.cache_max_age = 60*60*2 # stay in http cache for 2 hours by default