[CMF-checkins] SVN: CMF/trunk/CMFCore/ - CMFCore.FSDTMLMethod/FSImage/FSFile: Added 304 Not Modified support

Jens Vagelpohl jens at dataflake.org
Sat Feb 18 08:57:38 EST 2006


Log message for revision 41662:
  - CMFCore.FSDTMLMethod/FSImage/FSFile: Added 304 Not Modified support
    analogous to the support added for FSPageTemplates earlier on.
    (http://www.zope.org/Collectors/CMF/402)
  

Changed:
  U   CMF/trunk/CMFCore/FSDTMLMethod.py
  U   CMF/trunk/CMFCore/FSFile.py
  U   CMF/trunk/CMFCore/FSImage.py
  U   CMF/trunk/CMFCore/tests/base/dummy.py
  U   CMF/trunk/CMFCore/tests/test_FSDTMLMethod.py
  U   CMF/trunk/CMFCore/tests/test_FSFile.py
  U   CMF/trunk/CMFCore/tests/test_FSImage.py
  U   CMF/trunk/CMFCore/utils.py

-=-
Modified: CMF/trunk/CMFCore/FSDTMLMethod.py
===================================================================
--- CMF/trunk/CMFCore/FSDTMLMethod.py	2006-02-18 12:47:21 UTC (rev 41661)
+++ CMF/trunk/CMFCore/FSDTMLMethod.py	2006-02-18 13:57:37 UTC (rev 41662)
@@ -29,7 +29,7 @@
 from permissions import View
 from permissions import ViewManagementScreens
 from utils import _dtmldir
-from utils import _setCacheHeaders
+from utils import _setCacheHeaders, _checkConditionalGET
 from utils import expandpath
 
 
@@ -112,15 +112,19 @@
 
         self._updateFromFS()
 
+        kw['document_id']   =self.getId()
+        kw['document_title']=self.title
+
+        if client is not None:
+            if _checkConditionalGET(self, kw):
+                return ''
+
         if not self._cache_namespace_keys:
             data = self.ZCacheable_get(default=_marker)
             if data is not _marker:
                 # Return cached results.
                 return data
 
-        kw['document_id']   =self.getId()
-        kw['document_title']=self.title
-
         __traceback_info__ = self._filepath
         security=getSecurityManager()
         security.addContext(self)

Modified: CMF/trunk/CMFCore/FSFile.py
===================================================================
--- CMF/trunk/CMFCore/FSFile.py	2006-02-18 12:47:21 UTC (rev 41661)
+++ CMF/trunk/CMFCore/FSFile.py	2006-02-18 13:57:37 UTC (rev 41662)
@@ -22,7 +22,6 @@
 from OFS.Cache import Cacheable
 from zope.app.content_types import guess_content_type
 from OFS.Image import File
-from webdav.common import rfc1123_date
 
 from DirectoryView import registerFileExtension
 from DirectoryView import registerMetaType
@@ -32,7 +31,7 @@
 from permissions import ViewManagementScreens
 from utils import _dtmldir
 from utils import _setCacheHeaders, _ViewEmulator
-from utils import expandpath
+from utils import expandpath, _FSCacheHeaders, _checkConditionalGET
 
 
 class FSFile(FSObject):
@@ -104,6 +103,9 @@
         self._updateFromFS()
         return str( self._readFile( 0 ) )
 
+    def modified(self):
+        return self.getModTime()
+
     security.declareProtected(View, 'index_html')
     def index_html(self, REQUEST, RESPONSE):
         """
@@ -113,51 +115,38 @@
         Content-Type HTTP header to the objects content type.
         """
         self._updateFromFS()
-        data = self._readFile(0)
-        data_len = len(data)
-        last_mod = self._file_mod_time
-        status = 200
-        # HTTP If-Modified-Since header handling.
-        header=REQUEST.get_header('If-Modified-Since', None)
-        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=long(DateTime(header).timeTime())
-            except:
-                mod_since=None
-                
-            if mod_since is not None:
-                if last_mod > 0 and last_mod <= mod_since:
-                    status = 304
-                    data = ''
+        view = _ViewEmulator().__of__(self)
 
-        #Last-Modified will get stomped on by a cache policy it there is
-        #one set....
-        RESPONSE.setStatus(status)
-        RESPONSE.setHeader('Last-Modified', rfc1123_date(last_mod))
+        # 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)
 
-        if status != 304:
-            # Avoid setting content-length for a 304. See RFC 2616.
-            # Zope might still, for better or for worse, set a 
-            # content-length header with value "0".
-            RESPONSE.setHeader('Content-Length', data_len)
+        # old-style If-Modified-Since header handling.
+        if self._setOldCacheHeaders():
+            # Make sure the CachingPolicyManager gets a go as well
+            _setCacheHeaders(view, extra_context={})
+            return ''
 
+        data = self._readFile(0)
+        data_len = len(data)
+        RESPONSE.setHeader('Content-Length', data_len)
+
         #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(_ViewEmulator().__of__(self), extra_context={})
+            _setCacheHeaders(view, extra_context={})
         return data
 
+    def _setOldCacheHeaders(self):
+        # return False to disable this simple caching behaviour
+        return _FSCacheHeaders(self)
+
     security.declareProtected(View, 'getContentType')
     def getContentType(self):
         """Get the content type of a file or image.

Modified: CMF/trunk/CMFCore/FSImage.py
===================================================================
--- CMF/trunk/CMFCore/FSImage.py	2006-02-18 12:47:21 UTC (rev 41661)
+++ CMF/trunk/CMFCore/FSImage.py	2006-02-18 13:57:37 UTC (rev 41662)
@@ -18,7 +18,6 @@
 import Globals
 from DateTime import DateTime
 from AccessControl import ClassSecurityInfo
-from webdav.common import rfc1123_date
 from OFS.Cache import Cacheable
 from OFS.Image import Image, getImageInfo
 
@@ -30,7 +29,7 @@
 from FSObject import FSObject
 from utils import _dtmldir
 from utils import _setCacheHeaders, _ViewEmulator
-from utils import expandpath
+from utils import expandpath, _FSCacheHeaders, _checkConditionalGET
 
 
 class FSImage(FSObject):
@@ -94,52 +93,43 @@
         Returns the contents of the file or image.  Also, sets the
         Content-Type HTTP header to the objects content type.
         """
+
         self._updateFromFS()
-        data = self._data
-        data_len = len(data)
-        last_mod = self._file_mod_time
-        status = 200
-        # HTTP If-Modified-Since header handling.
-        header = REQUEST.get_header('If-Modified-Since', None)
-        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 = long(DateTime(header).timeTime())
-            except:
-                mod_since = None
+        view = _ViewEmulator().__of__(self)
 
-            if mod_since is not None:
-                if last_mod > 0 and last_mod <= mod_since:
-                    status = 304
-                    data = ''
+        # If we have a conditional get, set status 304 and return
+        # no content
+        if _checkConditionalGET(view, extra_context={}):
+            return ''
 
-        #Last-Modified will get stomped on by a cache policy it there is
-        #one set....
-        RESPONSE.setStatus(status)
-        RESPONSE.setHeader('Last-Modified', rfc1123_date(last_mod))
         RESPONSE.setHeader('Content-Type', self.content_type)
 
-        if status != 304:
-            # Avoid setting content-length for a 304. See RFC 2616.
-            # Zope might still, for better or for worse, set a
-            # content-length header with value "0". 
-            RESPONSE.setHeader('Content-Length', data_len)
+        # old-style If-Modified-Since header handling.
+        if self._setOldCacheHeaders():
+            # Make sure the CachingPolicyManager gets a go as well
+            _setCacheHeaders(view, extra_context={})
+            return ''
 
+        data = self._readFile(0)
+        data_len = len(data)
+        RESPONSE.setHeader('Content-Length', data_len)
+
         #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(_ViewEmulator().__of__(self), extra_context={})
+            _setCacheHeaders(view, extra_context={})
         return data
 
+    def _setOldCacheHeaders(self):
+        # return False to disable this simple caching behaviour
+        return _FSCacheHeaders(self)
+
+    def modified(self):
+        return self.getModTime()
+
     security.declareProtected(View, 'getContentType')
     def getContentType(self):
         """Get the content type of a file or image.

Modified: CMF/trunk/CMFCore/tests/base/dummy.py
===================================================================
--- CMF/trunk/CMFCore/tests/base/dummy.py	2006-02-18 12:47:21 UTC (rev 41661)
+++ CMF/trunk/CMFCore/tests/base/dummy.py	2006-02-18 13:57:37 UTC (rev 41662)
@@ -22,6 +22,8 @@
 from Products.CMFCore.PortalContent import PortalContent
 from security import OmnipotentUser
 
+from DateTime import DateTime
+from webdav.common import rfc1123_date
 
 class DummyObject(Implicit):
     """
@@ -351,7 +353,6 @@
     def notifyCreated(self, ob):
         self.test_notified = ob
 
-
 class DummyCachingManager:
 
     def getHTTPCachingHeaders( self, content, view_name, keywords, time=None ):
@@ -360,5 +361,32 @@
              ('test_path', '/'.join(content.getPhysicalPath())),
              )
 
+    def getModTimeAndETag(self, content, view_method, keywords, time=None ):
+         return (None, None, False)
+
     def getPhysicalPath(self):
         return ('baz',)
+
+
+FAKE_ETAG = None # '--FAKE ETAG--'
+
+class DummyCachingManagerWithPolicy(DummyCachingManager):
+
+    # dummy fixture implementing a single policy:
+    #  - always set the last-modified date if available
+    #  - calculate the date using the modified method on content
+
+    def getHTTPCachingHeaders( self, content, view_name, keywords, time=None ):
+
+         # if the object has a modified method, add it as last-modified
+         if hasattr(content, 'modified'):
+             headers = ( ('Last-modified', rfc1123_date(content.modified()) ), )
+         return headers
+
+    def getModTimeAndETag(self, content, view_method, keywords, time=None ):
+         modified_date = None
+         if hasattr(content, 'modified'):
+            modified_date = content.modified()
+         set_last_modified = (modified_date is not None)
+         return (modified_date, FAKE_ETAG, set_last_modified)
+

Modified: CMF/trunk/CMFCore/tests/test_FSDTMLMethod.py
===================================================================
--- CMF/trunk/CMFCore/tests/test_FSDTMLMethod.py	2006-02-18 12:47:21 UTC (rev 41661)
+++ CMF/trunk/CMFCore/tests/test_FSDTMLMethod.py	2006-02-18 13:57:37 UTC (rev 41662)
@@ -26,10 +26,13 @@
 from Products.CMFCore.FSDTMLMethod import FSDTMLMethod
 from Products.CMFCore.FSMetadata import FSMetadata
 from Products.CMFCore.tests.base.dummy import DummyCachingManager
+from Products.CMFCore.tests.base.dummy import DummyCachingManagerWithPolicy
 from Products.CMFCore.tests.base.testcase import FSDVTest
 from Products.CMFCore.tests.base.testcase import RequestTest
 from Products.CMFCore.tests.base.testcase import SecurityTest
+from Products.CMFCore.tests.base.dummy import DummyContent
 
+from DateTime import DateTime
 
 class FSDTMLMaker(FSDVTest):
 
@@ -66,7 +69,25 @@
         self.failUnless( 'foo' in self.RESPONSE.headers.keys() )
         self.failUnless( 'bar' in self.RESPONSE.headers.keys() )
 
+    def test_304_response_from_cpm( self ):
+        # test that we get a 304 response from the cpm via this template
 
+        from webdav.common import rfc1123_date
+
+        mod_time = DateTime()
+        self.root.caching_policy_manager = DummyCachingManagerWithPolicy()
+        content = DummyContent(id='content')
+        content.modified_date = mod_time
+        content = content.__of__(self.root)
+        script = self._makeOne('testDTML', 'testDTML.dtml')
+        script = script.__of__(content)
+        self.REQUEST.environ[ 'IF_MODIFIED_SINCE'
+                            ] = '%s;' % rfc1123_date( mod_time+3600 )
+        data = script(content, self.REQUEST, self.RESPONSE)
+
+        self.assertEqual( data, '' )
+        self.assertEqual( self.RESPONSE.getStatus(), 304 )
+
 class FSDTMLMethodCustomizationTests( SecurityTest, FSDTMLMaker ):
 
     def setUp( self ):

Modified: CMF/trunk/CMFCore/tests/test_FSFile.py
===================================================================
--- CMF/trunk/CMFCore/tests/test_FSFile.py	2006-02-18 12:47:21 UTC (rev 41661)
+++ CMF/trunk/CMFCore/tests/test_FSFile.py	2006-02-18 13:57:37 UTC (rev 41662)
@@ -21,6 +21,7 @@
 
 from os.path import join as path_join
 
+from Products.CMFCore.tests.base.dummy import DummyCachingManagerWithPolicy
 from Products.CMFCore.tests.base.dummy import DummyCachingManager
 from Products.CMFCore.tests.base.testcase import FSDVTest
 from Products.CMFCore.tests.base.testcase import RequestTest
@@ -142,6 +143,53 @@
         self.failUnless( data, '' )
         self.assertEqual( self.RESPONSE.getStatus(), 200 )
 
+    def test_index_html_with_304_from_cpm( self ):
+        self.root.caching_policy_manager = DummyCachingManagerWithPolicy()
+        path, ref = self._extractFile('test_file.swf')
+
+        import os
+        from webdav.common import rfc1123_date
+        from base.dummy import FAKE_ETAG
+        
+        file = self._makeOne( 'test_file', 'test_file.swf' )
+        file = file.__of__( self.root )
+
+        mod_time = os.stat( path )[ 8 ]
+
+        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('test_file.swf')
+
+        import os
+        from webdav.common import rfc1123_date
+        
+        file = self._makeOne( 'test_file', 'test_file.swf' )
+        file = file.__of__( self.root )
+
+        mod_time = os.stat( path )[ 8 ]
+
+        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)

Modified: CMF/trunk/CMFCore/tests/test_FSImage.py
===================================================================
--- CMF/trunk/CMFCore/tests/test_FSImage.py	2006-02-18 12:47:21 UTC (rev 41661)
+++ CMF/trunk/CMFCore/tests/test_FSImage.py	2006-02-18 13:57:37 UTC (rev 41662)
@@ -22,6 +22,7 @@
 from os.path import join as path_join
 
 from Products.CMFCore.tests.base.dummy import DummyCachingManager
+from Products.CMFCore.tests.base.dummy import DummyCachingManagerWithPolicy
 from Products.CMFCore.tests.base.testcase import FSDVTest
 from Products.CMFCore.tests.base.testcase import RequestTest
 
@@ -130,6 +131,28 @@
         self.failUnless( data, '' )
         self.assertEqual( self.RESPONSE.getStatus(), 200 )
 
+    def test_index_html_with_304_from_cpm( self ):
+        self.root.caching_policy_manager = DummyCachingManagerWithPolicy()
+        path, ref = self._extractFile()
+
+        import os
+        from webdav.common import rfc1123_date
+        from base.dummy import FAKE_ETAG
+        
+        file = self._makeOne( 'test_file', 'test_image.gif' )
+        file = file.__of__( self.root )
+
+        mod_time = os.stat( path )[ 8 ]
+
+        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)
@@ -142,7 +165,31 @@
         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()
 
+        import os
+        from webdav.common import rfc1123_date
+        
+        file = self._makeOne( 'test_file', 'test_image.gif' )
+        file = file.__of__( self.root )
+
+        mod_time = os.stat( path )[ 8 ]
+
+        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/gif' )
+        self.assertEqual( self.RESPONSE.getHeader( 'Last-Modified'.lower() )
+                        , rfc1123_date( mod_time ) )
+
     def test_index_html_with_304_and_caching( self ):
 
         # See collector #355

Modified: CMF/trunk/CMFCore/utils.py
===================================================================
--- CMF/trunk/CMFCore/utils.py	2006-02-18 12:47:21 UTC (rev 41661)
+++ CMF/trunk/CMFCore/utils.py	2006-02-18 13:57:37 UTC (rev 41662)
@@ -44,6 +44,7 @@
 from OFS.PropertySheets import PropertySheets
 from OFS.SimpleItem import SimpleItem
 from thread import allocate_lock
+from webdav.common import rfc1123_date
 from zope.i18nmessageid import MessageFactory
 
 from exceptions import AccessControl_Unauthorized
@@ -736,6 +737,42 @@
     return p.replace('\\','/')
 
 
+def _FSCacheHeaders(obj):
+    # Old-style setting 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 = obj._file_mod_time
+
+    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))
+
+
+
 class SimpleRecord:
     """ record-like class """
 



More information about the CMF-checkins mailing list