[CMF-checkins] SVN: CMF/branches/1.6/C -
CMFCore.FSDTMLMethod/FSImage/FSFile: Added 304 Not Modified support
Jens Vagelpohl
jens at dataflake.org
Sat Feb 18 08:57:53 EST 2006
Log message for revision 41663:
- 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/branches/1.6/CHANGES.txt
U CMF/branches/1.6/CMFCore/FSDTMLMethod.py
U CMF/branches/1.6/CMFCore/FSFile.py
U CMF/branches/1.6/CMFCore/FSImage.py
U CMF/branches/1.6/CMFCore/tests/base/dummy.py
U CMF/branches/1.6/CMFCore/tests/test_FSDTMLMethod.py
U CMF/branches/1.6/CMFCore/tests/test_FSFile.py
U CMF/branches/1.6/CMFCore/tests/test_FSImage.py
U CMF/branches/1.6/CMFCore/utils.py
-=-
Modified: CMF/branches/1.6/CHANGES.txt
===================================================================
--- CMF/branches/1.6/CHANGES.txt 2006-02-18 13:57:37 UTC (rev 41662)
+++ CMF/branches/1.6/CHANGES.txt 2006-02-18 13:57:52 UTC (rev 41663)
@@ -18,6 +18,10 @@
Features
+ - 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)
+
- Replaced Z2 interfaces w/ Z3 interfaces, dynamically creating Z2
interfaces via bridge
Modified: CMF/branches/1.6/CMFCore/FSDTMLMethod.py
===================================================================
--- CMF/branches/1.6/CMFCore/FSDTMLMethod.py 2006-02-18 13:57:37 UTC (rev 41662)
+++ CMF/branches/1.6/CMFCore/FSDTMLMethod.py 2006-02-18 13:57:52 UTC (rev 41663)
@@ -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/branches/1.6/CMFCore/FSFile.py
===================================================================
--- CMF/branches/1.6/CMFCore/FSFile.py 2006-02-18 13:57:37 UTC (rev 41662)
+++ CMF/branches/1.6/CMFCore/FSFile.py 2006-02-18 13:57:52 UTC (rev 41663)
@@ -22,7 +22,6 @@
from OFS.Cache import Cacheable
from OFS.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/branches/1.6/CMFCore/FSImage.py
===================================================================
--- CMF/branches/1.6/CMFCore/FSImage.py 2006-02-18 13:57:37 UTC (rev 41662)
+++ CMF/branches/1.6/CMFCore/FSImage.py 2006-02-18 13:57:52 UTC (rev 41663)
@@ -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/branches/1.6/CMFCore/tests/base/dummy.py
===================================================================
--- CMF/branches/1.6/CMFCore/tests/base/dummy.py 2006-02-18 13:57:37 UTC (rev 41662)
+++ CMF/branches/1.6/CMFCore/tests/base/dummy.py 2006-02-18 13:57:52 UTC (rev 41663)
@@ -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):
"""
@@ -350,7 +352,6 @@
def notifyCreated(self, ob):
self.test_notified = ob
-
class DummyCachingManager:
def getHTTPCachingHeaders( self, content, view_name, keywords, time=None ):
@@ -359,5 +360,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/branches/1.6/CMFCore/tests/test_FSDTMLMethod.py
===================================================================
--- CMF/branches/1.6/CMFCore/tests/test_FSDTMLMethod.py 2006-02-18 13:57:37 UTC (rev 41662)
+++ CMF/branches/1.6/CMFCore/tests/test_FSDTMLMethod.py 2006-02-18 13:57:52 UTC (rev 41663)
@@ -31,10 +31,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):
@@ -71,7 +74,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/branches/1.6/CMFCore/tests/test_FSFile.py
===================================================================
--- CMF/branches/1.6/CMFCore/tests/test_FSFile.py 2006-02-18 13:57:37 UTC (rev 41662)
+++ CMF/branches/1.6/CMFCore/tests/test_FSFile.py 2006-02-18 13:57:52 UTC (rev 41663)
@@ -24,6 +24,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
@@ -145,6 +146,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/branches/1.6/CMFCore/tests/test_FSImage.py
===================================================================
--- CMF/branches/1.6/CMFCore/tests/test_FSImage.py 2006-02-18 13:57:37 UTC (rev 41662)
+++ CMF/branches/1.6/CMFCore/tests/test_FSImage.py 2006-02-18 13:57:52 UTC (rev 41663)
@@ -25,6 +25,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
@@ -133,6 +134,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)
@@ -145,7 +168,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/branches/1.6/CMFCore/utils.py
===================================================================
--- CMF/branches/1.6/CMFCore/utils.py 2006-02-18 13:57:37 UTC (rev 41662)
+++ CMF/branches/1.6/CMFCore/utils.py 2006-02-18 13:57:52 UTC (rev 41663)
@@ -48,7 +48,7 @@
from Products.PageTemplates.Expressions import SecureModuleImporter
from StructuredText.StructuredText import HTML
from thread import allocate_lock
-
+from webdav.common import rfc1123_date
from exceptions import AccessControl_Unauthorized
from exceptions import NotFound
@@ -190,7 +190,39 @@
raise NotFound('Cannot find default view for "%s"' %
'/'.join(obj.getPhysicalPath()))
+def _FSCacheHeaders(obj):
+ 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))
+
# If Zope ever provides a call to getRolesInContext() through
# the SecurityManager API, the method below needs to be updated.
security.declarePrivate('_limitGrantedRoles')
More information about the CMF-checkins
mailing list