--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/httpcache.py Wed Nov 05 15:52:50 2008 +0100
@@ -0,0 +1,131 @@
+"""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