[Zope-Checkins] SVN: Zope/branches/slinkp-httpcache-improvements-branch/lib/python/Products/StandardCacheManagers/ Added feature to strip a prefix from paths to purge.

Paul Winkler pw_lists at slinkp.com
Fri May 5 00:17:02 EDT 2006


Log message for revision 67983:
  Added feature to strip a prefix from paths to purge.
  This helps purging in some virtual hosting situations.
  No UI for it yet.  Default path is the folder where
  the cache manager lives. (It updates on cache manager move, too.)
  
  

Changed:
  U   Zope/branches/slinkp-httpcache-improvements-branch/lib/python/Products/StandardCacheManagers/AcceleratedHTTPCacheManager.py
  U   Zope/branches/slinkp-httpcache-improvements-branch/lib/python/Products/StandardCacheManagers/tests/test_AcceleratedHTTPCacheManager.py

-=-
Modified: Zope/branches/slinkp-httpcache-improvements-branch/lib/python/Products/StandardCacheManagers/AcceleratedHTTPCacheManager.py
===================================================================
--- Zope/branches/slinkp-httpcache-improvements-branch/lib/python/Products/StandardCacheManagers/AcceleratedHTTPCacheManager.py	2006-05-05 03:38:36 UTC (rev 67982)
+++ Zope/branches/slinkp-httpcache-improvements-branch/lib/python/Products/StandardCacheManagers/AcceleratedHTTPCacheManager.py	2006-05-05 04:17:02 UTC (rev 67983)
@@ -15,6 +15,13 @@
   Adds caching headers to the response so that downstream caches will
   cache according to a common policy.
 
+XXX TODO: Consider adding features from this patch:
+http://www.zope.org/Members/mtb/index_html/AcceleratedHTTPCacheManager-headers.diff/file_view
+
+XXX TODO: Add UI for strip_root_paths feature.
+
+XXX TODO: update help.stx.
+
 $Id$
 '''
 
@@ -40,6 +47,7 @@
     # Also note that objects of this class are not persistent,
     # nor do they use acquisition.
 
+    strip_root_paths = 0
     connection_factory = httplib.HTTPConnection
 
     def __init__(self):
@@ -59,7 +67,8 @@
         # any kind of fuzzy purging; we have to specify exactly the
         # URL to purge.  So we try to purge the known paths most
         # likely to turn up in practice: the physical path and the
-        # current absolute_url_path.  Any of those can be
+        # current absolute_url_path, optionally with the cache
+        # manager's containment path stripped off. Any of those can be
         # wrong in some circumstances, but it may be the best we can
         # do :-(
         # It would be nice if Squid's purge feature was better
@@ -68,19 +77,35 @@
         phys_path = ob.getPhysicalPath()
         if self.hit_counts.has_key(phys_path):
             del self.hit_counts[phys_path]
-        purge_paths = (ob.absolute_url_path(), quote('/'.join(phys_path)))
+        self.notify_urls = [u.strip() for u in self.notify_urls
+                            if u.strip()]
+        if not self.notify_urls:
+            return
+        purge_paths = [ob.absolute_url_path(), quote('/'.join(phys_path))]
         # Don't purge the same path twice.
         if purge_paths[0] == purge_paths[1]:
+            purge_paths = purge_paths[:1]
+        if self.strip_root_paths:
+            # Treat root_path as the physical root of the site, i.e.
+            # left-strip it from all purge paths. Strip path segments,
+            # not just substrings.
+            logger.debug('Stripping %s from paths' % self.root_path)
+            root_path = self.root_path.split('/')
+            for i, path in enumerate(purge_paths):
+                path_parts = path.split('/')
+                if path_parts[:len(root_path)] == root_path:
+                    new_path = '/'.join(path_parts[len(root_path):])
+                    if path.startswith('/') and not new_path.startswith('/'):
+                        new_path = '/' + new_path
+                    purge_paths[i] = new_path
+        # Often phsyical == virtual, don't purge the same path twice.
+        if purge_paths[-1] == purge_paths[0]:
             purge_paths  = purge_paths[:1]
         results = []
-        for url in self.notify_urls:
-            if not url.strip():
-                continue
+        for u in self.notify_urls:
             # Send the PURGE request to each HTTP accelerator.
-            if url[:7].lower() == 'http://':
-                u = url
-            else:
-                u = 'http://' + url
+            if u[:7].lower() != 'http://':
+                u = 'http://' + u
             (scheme, host, path, params, query, fragment
              ) = urlparse.urlparse(u)
             if path.lower().startswith('/http://'):
@@ -98,7 +123,7 @@
                     msg = 'socket.gaierror: maybe the server ' + \
                           'at %s is down, or the cache manager ' + \
                           'is misconfigured?'
-                    logger.error(msg % url)
+                    logger.error(msg % u)
                     continue
                 r = h.getresponse()
                 status = '%s %s' % (r.status, r.reason)
@@ -135,8 +160,9 @@
             return
         # Set HTTP Expires and Cache-Control headers
         seconds=self.interval
-        expires=rfc1123_date(time.time() + seconds)
-        RESPONSE.setHeader('Last-Modified',rfc1123_date(time.time()))
+        now = time.time()
+        expires=rfc1123_date(now + seconds)
+        RESPONSE.setHeader('Last-Modified',rfc1123_date(now))
         RESPONSE.setHeader('Cache-Control', 'max-age=%d' % seconds)
         RESPONSE.setHeader('Expires', expires)
 
@@ -164,9 +190,30 @@
         self.title = ''
         self._settings = {'anonymous_only':1,
                           'interval':3600,
-                          'notify_urls':()}
+                          'notify_urls':(),
+                          'root_path':'',
+                          'strip_root_paths':0,
+                          }
         self.__cacheid = '%s_%f' % (id(self), time.time())
 
+    security.declarePrivate('manage_afterAdd')
+    def manage_afterAdd(self, item, container):
+        CacheManager.manage_afterAdd(self, item, container)
+        SimpleItem.manage_afterAdd(self, item, container)
+        if not self._settings['root_path'].strip():
+            path =  '/'.join(container.getPhysicalPath())
+            self._settings['root_path'] = path
+            self._p_changed = 1
+
+    security.declarePrivate('manage_beforeDelete')
+    def manage_beforeDelete(self, item, container):
+        CacheManager.manage_beforeDelete(self, item, container)
+        SimpleItem.manage_beforeDelete(self, item, container)
+        if ( self._settings['root_path'].strip() ==
+             '/'.join(container.getPhysicalPath())):
+            self._settings['root_path'] = ''
+            self._p_changed = 1
+
     def getId(self):
         ' '
         return self.id
@@ -199,7 +246,11 @@
         self._settings = {
             'anonymous_only':settings.get('anonymous_only') and 1 or 0,
             'interval':int(settings['interval']),
-            'notify_urls':tuple(settings['notify_urls']),}
+            'notify_urls':tuple(settings['notify_urls']),
+            # XXX Add root_path and strip_root_paths to UI.
+            'root_path':'/'.join(self.getPhysicalPath()[:-1]),
+            'strip_root_paths':int(settings.get('strip_root_paths', 1)),
+            }
         cache = self.ZCacheManager_getCache()
         cache.initSettings(self._settings)
         if REQUEST is not None:
@@ -256,7 +307,6 @@
 
 InitializeClass(AcceleratedHTTPCacheManager)
 
-
 manage_addAcceleratedHTTPCacheManagerForm = DTMLFile('dtml/addAccel',
                                                      globals())
 

Modified: Zope/branches/slinkp-httpcache-improvements-branch/lib/python/Products/StandardCacheManagers/tests/test_AcceleratedHTTPCacheManager.py
===================================================================
--- Zope/branches/slinkp-httpcache-improvements-branch/lib/python/Products/StandardCacheManagers/tests/test_AcceleratedHTTPCacheManager.py	2006-05-05 03:38:36 UTC (rev 67982)
+++ Zope/branches/slinkp-httpcache-improvements-branch/lib/python/Products/StandardCacheManagers/tests/test_AcceleratedHTTPCacheManager.py	2006-05-05 04:17:02 UTC (rev 67983)
@@ -11,12 +11,13 @@
 # FOR A PARTICULAR PURPOSE.
 #
 ##############################################################################
-""" Unit tests for AcceleratedCacheManager module.
+""" Unit tests for AcceleratedHTTPCacheManager module.
 
 $Id$
 """
 
 import unittest
+from OFS.Folder import Folder
 from Products.StandardCacheManagers.AcceleratedHTTPCacheManager \
      import AcceleratedHTTPCache, AcceleratedHTTPCacheManager
 
@@ -107,7 +108,35 @@
         self.assertEqual(requests[0]['path'], dummy.absolute_url_path())
         self.assertEqual(requests[1]['path'], dummy.path)
 
+    def test_strip_root_paths(self):
+        # More control of purge paths for virtual hosting features.
+        cache = self._makeOne()
+        cache.notify_urls = ['http://foo.com']
+        cache.connection_factory, requests = MockConnectionClassFactory()
+        cache.strip_root_paths = True
+        dummy = DummyObject(path='/i/live/here',
+                            urlpath='/published/elsewhere')
+        cache.root_path = '/i/live'
+        cache.ZCache_invalidate(dummy)
+        # That should fire off two invalidations...
+        self.assertEqual(requests[0]['path'], '/published/elsewhere')
+        self.assertEqual(requests[1]['path'], '/here')
+        cache.connection_factory, requests = MockConnectionClassFactory()
+        cache.root_path = '/published'
+        cache.ZCache_invalidate(dummy)
+        self.assertEqual(requests[0]['path'], '/elsewhere')
+        self.assertEqual(requests[1]['path'], '/i/live/here')
+        # Should only affect full path segments, not things that
+        # happen to start with the same substring.
+        cache.root_path = '/pub'
+        dummy = DummyObject(path='/public/stuff',
+                            urlpath='/pub/food')
+        cache.connection_factory, requests = MockConnectionClassFactory()
+        cache.ZCache_invalidate(dummy)
+        self.assertEqual(requests[0]['path'], '/food')
+        self.assertEqual(requests[1]['path'], '/public/stuff')
 
+
 class CacheManagerTests(unittest.TestCase):
 
     def _getTargetClass(self):
@@ -117,7 +146,6 @@
         return self._getTargetClass()(*args, **kw)
 
     def _makeContext(self):
-        from OFS.Folder import Folder
         root = Folder()
         root.getPhysicalPath = lambda: ('', 'some_path',)
         cm_id = 'http_cache'
@@ -141,8 +169,36 @@
         self.assert_('anonymous_only' in settings.keys())
         self.assert_('interval' in settings.keys())
         self.assert_('notify_urls' in settings.keys())
+        self.assert_('root_path' in settings.keys())
+        self.assert_('strip_root_paths' in settings.keys())
 
+    def test_default_root_path(self):
+        root1, cachemanager1 = self._makeContext()
+        self.assertEqual(cachemanager1._settings['root_path'],
+                         '/'.join(root1.getPhysicalPath()))
+        manager_id = cachemanager1.getId()
+        # Rather than do whole-hog copy/paste, which requires
+        # more annoying setup, we'll just do _setObject() and
+        # call manage_beforeDelete() manually.
+        cachemanager1.manage_beforeDelete(cachemanager1, root1)
+        root2 = Folder()
+        ROOT2_PATH = '/some/new/place'
+        root2.getPhysicalPath = lambda: ('','someplace','else')
+        root2._setObject(manager_id, cachemanager1)
+        cachemanager2 = root2[manager_id]
+        # The path should update to match the new location.
+        self.assertEqual(cachemanager2._settings['root_path'],
+                         '/'.join(root2.getPhysicalPath()))
+        # But if a path was already configured, it should be left alone.
+        root3, cachemanager3 = self._makeContext()
+        ORIG_PATH='/leave/me/alone'
+        cachemanager3._settings['root_path'] =  ORIG_PATH
+        cachemanager3.manage_beforeDelete(cachemanager3, root3)
+        root1._setObject('new_manager', cachemanager3)
+        self.assertEqual(root1['new_manager']._settings['root_path'],
+                         ORIG_PATH)
 
+
 def test_suite():
     suite = unittest.TestSuite()
     suite.addTest(unittest.makeSuite(AcceleratedHTTPCacheTests))



More information about the Zope-Checkins mailing list