[Checkins] SVN: CMF/trunk/C - CMFDefault.Image and CMFDefault.File: Overridden index_html methods

Jens Vagelpohl jens at dataflake.org
Wed Oct 11 14:47:52 EDT 2006


Log message for revision 70604:
  - CMFDefault.Image and CMFDefault.File: Overridden index_html methods
    add Cache Policy Manager-awareness and thus bring these implementations
    in line with CMFCore.FSFile and CMFCore.FSImage
    (http://www.zope.org/Collectors/CMF/454) 
  

Changed:
  U   CMF/trunk/CHANGES.txt
  U   CMF/trunk/CMFCore/utils.py
  U   CMF/trunk/CMFDefault/File.py
  U   CMF/trunk/CMFDefault/Image.py
  A   CMF/trunk/CMFDefault/tests/TestFile.swf
  U   CMF/trunk/CMFDefault/tests/test_File.py
  U   CMF/trunk/CMFDefault/tests/test_Image.py

-=-
Modified: CMF/trunk/CHANGES.txt
===================================================================
--- CMF/trunk/CHANGES.txt	2006-10-11 15:57:25 UTC (rev 70603)
+++ CMF/trunk/CHANGES.txt	2006-10-11 18:47:51 UTC (rev 70604)
@@ -1,3 +1,13 @@
+CMF 2.1-beta (unreleased)
+
+  New Features
+
+    - CMFDefault.Image and CMFDefault.File: Overridden index_html methods
+      add Cache Policy Manager-awareness and thus bring these implementations
+      in line with CMFCore.FSFile and CMFCore.FSImage
+      (http://www.zope.org/Collectors/CMF/454)
+
+
 CMF 2.1-alpha (2006/10/09)
 
   New Features

Modified: CMF/trunk/CMFCore/utils.py
===================================================================
--- CMF/trunk/CMFCore/utils.py	2006-10-11 15:57:25 UTC (rev 70603)
+++ CMF/trunk/CMFCore/utils.py	2006-10-11 18:47:51 UTC (rev 70604)
@@ -742,9 +742,42 @@
             break
     return p.replace('\\','/')
 
+def _OldCacheHeaders(obj):
+    # Old-style checking of modified headers
 
+    REQUEST = getattr(obj, 'REQUEST', None)
+    if REQUEST is None:
+        return False
+
+    RESPONSE = REQUEST.RESPONSE
+    header = REQUEST.get_header('If-Modified-Since', None)
+    last_mod = long(obj.modified().timeTime())
+
+    if header is not None:
+        header = header.split(';')[0]
+        # Some proxies seem to send invalid date strings for this
+        # header. If the date string is not valid, we ignore it
+        # rather than raise an error to be generally consistent
+        # with common servers such as Apache (which can usually
+        # understand the screwy date string as a lucky side effect
+        # of the way they parse it).
+        try:
+            mod_since=DateTime(header)
+            mod_since=long(mod_since.timeTime())
+        except TypeError:
+            mod_since=None
+               
+        if mod_since is not None:
+            if last_mod > 0 and last_mod <= mod_since:
+                RESPONSE.setStatus(304)
+                return True
+
+    #Last-Modified will get stomped on by a cache policy if there is
+    #one set....
+    RESPONSE.setHeader('Last-Modified', rfc1123_date(last_mod))
+
 def _FSCacheHeaders(obj):
-    # Old-style setting of modified headers
+    # Old-style setting of modified headers for FS-based objects
 
     REQUEST = getattr(obj, 'REQUEST', None)
     if REQUEST is None:

Modified: CMF/trunk/CMFDefault/File.py
===================================================================
--- CMF/trunk/CMFDefault/File.py	2006-10-11 15:57:25 UTC (rev 70603)
+++ CMF/trunk/CMFDefault/File.py	2006-10-11 18:47:51 UTC (rev 70604)
@@ -24,6 +24,10 @@
 from zope.interface import implements
 
 from Products.CMFCore.PortalContent import PortalContent
+from Products.CMFCore.utils import _OldCacheHeaders
+from Products.CMFCore.utils import _setCacheHeaders
+from Products.CMFCore.utils import _ViewEmulator
+from Products.CMFCore.utils import _checkConditionalGET
 from Products.GenericSetup.interfaces import IDAVAware
 
 from DublinCore import DefaultDublinCoreImpl
@@ -164,6 +168,45 @@
         self._edit( precondition, file )
         self.reindexObject()
 
+    security.declareProtected(View, 'index_html')
+    def index_html(self, REQUEST, RESPONSE):
+        """
+        The default view of the contents of a File or Image.
+
+        Returns the contents of the file or image.  Also, sets the
+        Content-Type HTTP header to the objects content type.
+        """
+        view = _ViewEmulator().__of__(self)
+
+        # If we have a conditional get, set status 304 and return
+        # no content 
+        if _checkConditionalGET(view, extra_context={}):
+            return ''
+
+        RESPONSE.setHeader('Content-Type', self.content_type)
+
+        # old-style If-Modified-Since header handling.
+        if self._setOldCacheHeaders():
+            # Make sure the CachingPolicyManager gets a go as well
+            _setCacheHeaders(view, extra_context={})
+            return ''
+
+        rendered = OFS.Image.File.index_html(self, REQUEST, RESPONSE)
+
+        # There are 2 Cache Managers which can be in play....
+        # need to decide which to use to determine where the cache headers
+        # are decided on.
+        if self.ZCacheable_getManager() is not None:
+            self.ZCacheable_set(None)
+        else:
+            _setCacheHeaders(view, extra_context={})
+
+        return rendered
+
+    def _setOldCacheHeaders(self):
+        # return False to disable this simple caching behaviour
+        return _OldCacheHeaders(self) 
+
     security.declareProtected(View, 'download')
     def download(self, REQUEST, RESPONSE):
         """Download this item.
@@ -180,7 +223,7 @@
 
         RESPONSE.setHeader('Content-Disposition',
                            'attachment; filename=%s' % self.getId())
-        return OFS.Image.File.index_html(self, REQUEST, RESPONSE)
+        return self.index_html(self, REQUEST, RESPONSE)
 
     security.declareProtected(View, 'Format')
     def Format(self):

Modified: CMF/trunk/CMFDefault/Image.py
===================================================================
--- CMF/trunk/CMFDefault/Image.py	2006-10-11 15:57:25 UTC (rev 70603)
+++ CMF/trunk/CMFDefault/Image.py	2006-10-11 18:47:51 UTC (rev 70604)
@@ -18,11 +18,16 @@
 
 import OFS.Image
 from AccessControl import ClassSecurityInfo
+from DateTime.DateTime import DateTime
 from Globals import InitializeClass
 from zope.component.factory import Factory
 from zope.interface import implements
 
 from Products.CMFCore.PortalContent import PortalContent
+from Products.CMFCore.utils import _OldCacheHeaders
+from Products.CMFCore.utils import _setCacheHeaders
+from Products.CMFCore.utils import _ViewEmulator
+from Products.CMFCore.utils import _checkConditionalGET
 from Products.GenericSetup.interfaces import IDAVAware
 
 from DublinCore import DefaultDublinCoreImpl
@@ -166,10 +171,34 @@
         Display the image, with or without standard_html_[header|footer],
         as appropriate.
         """
-        #if REQUEST['PATH_INFO'][-10:] == 'index_html':
-        #    return self.view(self, REQUEST)
-        return OFS.Image.Image.index_html(self, REQUEST, RESPONSE)
+        view = _ViewEmulator().__of__(self)
 
+        # If we have a conditional get, set status 304 and return
+        # no content
+        if _checkConditionalGET(view, extra_context={}):
+            return ''
+
+        # old-style If-Modified-Since header handling.
+        if self._setOldCacheHeaders():
+            # Make sure the CachingPolicyManager gets a go as well
+            _setCacheHeaders(view, extra_context={})
+            return ''
+
+        rendered = OFS.Image.Image.index_html(self, REQUEST, RESPONSE)
+
+        if self.ZCacheable_getManager() is None:
+            # not none cache manager already taken care of
+            _setCacheHeaders(view, extra_context={})
+        else:
+            self.ZCacheable_set(None)
+
+        return rendered
+
+    security.declarePrivate('_setOldCacheHeaders')
+    def _setOldCacheHeaders(self):
+        # return False to disable this simple caching behaviour
+        return _OldCacheHeaders(self)
+
     security.declareProtected(View, 'Format')
     def Format(self):
         """ Dublin Core element - resource format """

Added: CMF/trunk/CMFDefault/tests/TestFile.swf
===================================================================
(Binary files differ)


Property changes on: CMF/trunk/CMFDefault/tests/TestFile.swf
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Modified: CMF/trunk/CMFDefault/tests/test_File.py
===================================================================
--- CMF/trunk/CMFDefault/tests/test_File.py	2006-10-11 15:57:25 UTC (rev 70603)
+++ CMF/trunk/CMFDefault/tests/test_File.py	2006-10-11 18:47:51 UTC (rev 70604)
@@ -21,10 +21,14 @@
 from os.path import join as path_join
 
 from Products.CMFCore.testing import ConformsToContent
+from Products.CMFCore.tests.base.dummy import DummyCachingManagerWithPolicy
+from Products.CMFCore.tests.base.dummy import DummyCachingManager
+from Products.CMFCore.tests.base.testcase import RequestTest 
 from Products.CMFDefault import tests
 
 TESTS_HOME = tests.__path__[0]
 TEST_JPG = path_join(TESTS_HOME, 'TestImage.jpg')
+TEST_SWF = path_join(TESTS_HOME, 'TestFile.swf')
 
 
 class FileTests(ConformsToContent, unittest.TestCase):
@@ -69,10 +73,90 @@
         self.assertEqual(file.content_type, 'image/jpeg')
 
 
+class CachingTests(RequestTest):
+
+    def _getTargetClass(self):
+        from Products.CMFDefault.File import File
+
+        return File
+
+    def _makeOne(self, *args, **kw):
+        return self._getTargetClass()(*args, **kw)
+
+    def _extractFile( self ):
+
+        f = open( TEST_SWF, 'rb' )
+        try:
+            data = f.read()
+        finally:
+            f.close()
+
+        return TEST_SWF, data
+
+    def test_index_html_with_304_from_cpm( self ):
+        self.root.caching_policy_manager = DummyCachingManagerWithPolicy()
+        path, ref = self._extractFile()
+
+        from webdav.common import rfc1123_date
+        from Products.CMFCore.tests.base.dummy import FAKE_ETAG
+        
+        file = self._makeOne( 'test_file', 'test_file.swf', file=ref )
+        file = file.__of__( self.root )
+
+        mod_time = file.modified().timeTime()
+
+        self.REQUEST.environ[ 'IF_MODIFIED_SINCE'
+                            ] = '%s;' % rfc1123_date( mod_time )
+        self.REQUEST.environ[ 'IF_NONE_MATCH'
+                            ] = '%s;' % FAKE_ETAG
+
+        data = file.index_html( self.REQUEST, self.RESPONSE )
+        self.assertEqual( len(data), 0 )
+        self.assertEqual( self.RESPONSE.getStatus(), 304 )
+
+    def test_index_html_200_with_cpm( self ):
+        # should behave the same as without cpm installed
+        self.root.caching_policy_manager = DummyCachingManager()
+        path, ref = self._extractFile()
+
+        from webdav.common import rfc1123_date
+        
+        file = self._makeOne( 'test_file', 'test_file.swf', file=ref )
+        file = file.__of__( self.root )
+
+        mod_time = file.modified().timeTime()
+
+        data = file.index_html( self.REQUEST, self.RESPONSE )
+
+        self.assertEqual( len( data ), len( ref ) )
+        self.assertEqual( data, ref )
+        # ICK!  'HTTPResponse.getHeader' doesn't case-flatten the key!
+        self.assertEqual( self.RESPONSE.getHeader( 'Content-Length'.lower() )
+                        , str(len(ref)) )
+        self.assertEqual( self.RESPONSE.getHeader( 'Content-Type'.lower() )
+                        , 'application/octet-stream' )
+        self.assertEqual( self.RESPONSE.getHeader( 'Last-Modified'.lower() )
+                        , rfc1123_date( mod_time ) )
+
+    def test_caching( self ):
+        self.root.caching_policy_manager = DummyCachingManager()
+        original_len = len(self.RESPONSE.headers)
+        file = self._makeOne('test_file', 'test_file.swf')
+        file = file.__of__(self.root)
+        file.index_html(self.REQUEST, self.RESPONSE)
+        headers = self.RESPONSE.headers
+        self.failUnless(len(headers) >= original_len + 3)
+        self.failUnless('foo' in headers.keys())
+        self.failUnless('bar' in headers.keys())
+        self.assertEqual(headers['test_path'], '/test_file')
+
+
 def test_suite():
     return unittest.TestSuite((
         unittest.makeSuite(FileTests),
+        unittest.makeSuite(CachingTests),
         ))
 
 if __name__ == '__main__':
     unittest.main(defaultTest='test_suite')
+

Modified: CMF/trunk/CMFDefault/tests/test_Image.py
===================================================================
--- CMF/trunk/CMFDefault/tests/test_Image.py	2006-10-11 15:57:25 UTC (rev 70603)
+++ CMF/trunk/CMFDefault/tests/test_Image.py	2006-10-11 18:47:51 UTC (rev 70604)
@@ -32,9 +32,12 @@
 from zope.testing.cleanup import cleanUp
 
 from Products.CMFCore.testing import ConformsToContent
+from Products.CMFCore.tests.base.dummy import DummyCachingManager
+from Products.CMFCore.tests.base.dummy import DummyCachingManagerWithPolicy
 from Products.CMFCore.tests.base.dummy import DummySite
 from Products.CMFCore.tests.base.dummy import DummyTool
 from Products.CMFCore.tests.base.security import OmnipotentUser
+from Products.CMFCore.tests.base.testcase import RequestTest
 from Products.CMFCore.tests.base.testcase import SecurityRequestTest
 from Products.CMFCore.tests.base.testcase import setUpEvents
 from Products.CMFCore.tests.base.testcase import setUpGenericSetup
@@ -197,11 +200,118 @@
         review_state = self.workflow.getInfoFor(self.site.image2, 'review_state')
         self.assertEqual(review_state, 'published')
 
+class TestCaching(RequestTest):
 
+    def _extractFile( self ):
+
+        f = open( TEST_JPG, 'rb' )
+        try:
+            data = f.read()
+        finally:
+            f.close()
+
+        return TEST_JPG, data
+
+    def _getTargetClass(self):
+        from Products.CMFDefault.Image import Image
+
+        return Image
+
+    def _makeOne(self, *args, **kw):
+        return self._getTargetClass()(*args, **kw)
+
+    def test_index_html_with_304_from_cpm( self ):
+        self.root.caching_policy_manager = DummyCachingManagerWithPolicy()
+        path, ref = self._extractFile()
+
+        from webdav.common import rfc1123_date
+        from Products.CMFCore.tests.base.dummy import FAKE_ETAG
+
+        self.root.file = self._makeOne('test_file', 'test_image.jpg', file=ref)
+        file = self.root.file
+
+        mod_time = file.modified()
+
+        self.REQUEST.environ[ 'IF_MODIFIED_SINCE'
+                            ] = '%s;' % rfc1123_date( mod_time )
+        self.REQUEST.environ[ 'IF_NONE_MATCH'
+                            ] = '%s;' % FAKE_ETAG
+
+        data = file.index_html( self.REQUEST, self.RESPONSE )
+        self.assertEqual( len(data), 0 )
+        self.assertEqual( self.RESPONSE.getStatus(), 304 )
+
+    def test_caching( self ):
+        self.root.caching_policy_manager = DummyCachingManager()
+        original_len = len(self.RESPONSE.headers)
+        image = self._makeOne('test_image', 'test_image.jpg')
+        image = image.__of__(self.root)
+        image.index_html(self.REQUEST, self.RESPONSE)
+        headers = self.RESPONSE.headers
+        self.failUnless(len(headers) >= original_len + 3)
+        self.failUnless('foo' in headers.keys())
+        self.failUnless('bar' in headers.keys())
+        self.assertEqual(headers['test_path'], '/test_image')
+
+    def test_index_html_200_with_cpm( self ):
+        self.root.caching_policy_manager = DummyCachingManagerWithPolicy()
+        path, ref = self._extractFile()
+
+        from webdav.common import rfc1123_date
+
+        file = self._makeOne( 'test_file', 'test_image.jpg', file=ref )
+        file = file.__of__( self.root )
+
+        mod_time = file.modified()
+
+        data = file.index_html( self.REQUEST, self.RESPONSE )
+
+        # should behave the same as without cpm
+        self.assertEqual( len( data ), len( ref ) )
+        self.assertEqual( data, ref )
+        # ICK!  'HTTPResponse.getHeader' doesn't case-flatten the key!
+        self.assertEqual( self.RESPONSE.getHeader( 'Content-Length'.lower() )
+                        , str(len(ref)) )
+        self.assertEqual( self.RESPONSE.getHeader( 'Content-Type'.lower() )
+                        , 'image/jpeg' )
+        self.assertEqual( self.RESPONSE.getHeader( 'Last-Modified'.lower() )
+                        , rfc1123_date( mod_time ) )
+
+    def test_index_html_with_304_and_caching( self ):
+
+        # See collector #355
+        self.root.caching_policy_manager = DummyCachingManager()
+        original_len = len(self.RESPONSE.headers)
+        path, ref = self._extractFile()
+
+        from webdav.common import rfc1123_date
+
+        self.root.image = self._makeOne( 'test_image', 'test_image.gif' )
+        image = self.root.image
+        import transaction
+        transaction.savepoint(optimistic=True)
+
+        mod_time = image.modified()
+
+        self.REQUEST.environ[ 'IF_MODIFIED_SINCE'
+                            ] = '%s;' % rfc1123_date( mod_time+1 )
+
+        data = image.index_html( self.REQUEST, self.RESPONSE )
+
+        self.assertEqual( data, '' )
+        self.assertEqual( self.RESPONSE.getStatus(), 304 )
+
+        headers = self.RESPONSE.headers
+        self.failUnless(len(headers) >= original_len + 3)
+        self.failUnless('foo' in headers.keys())
+        self.failUnless('bar' in headers.keys())
+        self.assertEqual(headers['test_path'], '/test_image') 
+
 def test_suite():
     return unittest.TestSuite((
         unittest.makeSuite(TestImageElement),
         unittest.makeSuite(TestImageCopyPaste),
+        unittest.makeSuite(TestCaching),
         ))
 
 if __name__ == '__main__':



More information about the Checkins mailing list