[CMF-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 CMF-checkins
mailing list