[CMF-checkins]
SVN: CMF/branches/sidnei-cachingpolicymanager-backport-branch/CMFCore/
- Backport of geoffd-cachingpolicymanager-branch
Sidnei da Silva
sidnei at enfoldsystems.com
Thu Sep 8 22:33:38 EDT 2005
Log message for revision 38413:
- Backport of geoffd-cachingpolicymanager-branch
to CMF 1.4
Changed:
U CMF/branches/sidnei-cachingpolicymanager-backport-branch/CMFCore/CachingPolicyManager.py
U CMF/branches/sidnei-cachingpolicymanager-backport-branch/CMFCore/FSPageTemplate.py
U CMF/branches/sidnei-cachingpolicymanager-backport-branch/CMFCore/dtml/cachingPolicies.dtml
_U CMF/branches/sidnei-cachingpolicymanager-backport-branch/CMFCore/tests/fake_skins/fake_skin/testDTML.dtml
_U CMF/branches/sidnei-cachingpolicymanager-backport-branch/CMFCore/tests/fake_skins/fake_skin/testDTML.dtml.metadata
A CMF/branches/sidnei-cachingpolicymanager-backport-branch/CMFCore/tests/framework.py
U CMF/branches/sidnei-cachingpolicymanager-backport-branch/CMFCore/tests/test_CachingPolicyManager.py
A CMF/branches/sidnei-cachingpolicymanager-backport-branch/CMFCore/tests/test_Template304Handling.py
U CMF/branches/sidnei-cachingpolicymanager-backport-branch/CMFCore/utils.py
-=-
Modified: CMF/branches/sidnei-cachingpolicymanager-backport-branch/CMFCore/CachingPolicyManager.py
===================================================================
--- CMF/branches/sidnei-cachingpolicymanager-backport-branch/CMFCore/CachingPolicyManager.py 2005-09-09 02:26:08 UTC (rev 38412)
+++ CMF/branches/sidnei-cachingpolicymanager-backport-branch/CMFCore/CachingPolicyManager.py 2005-09-09 02:33:38 UTC (rev 38413)
@@ -57,7 +57,10 @@
if time is None:
time = DateTime()
+ # The name "content" is deprecated and will go away in CMF 1.7,
+ # please use "object" in your policy
data = { 'content' : content
+ , 'object' : content
, 'view' : view_method
, 'keywords' : keywords
, 'request' : getattr( content, 'REQUEST', {} )
@@ -99,6 +102,10 @@
the "Cache-control" header will be set using 'max_age_secs',
if passed; it should be an integer value in seconds.
+ - The "s-maxage" token of the "Cache-control" header will be
+ set using 's_max_age_secs', if passed; it should be an integer
+ value in seconds.
+
- The "Vary" HTTP response headers will be set if a value is
provided. The Vary header is described in RFC 2616. In essence,
it instructs caches that respect this header (such as Squid
@@ -108,6 +115,10 @@
Cookie headers into account when deciding what cached object to
choose and serve in response to a request.
+ - The "ETag" HTTP response header will be set if a value is
+ provided. The value is a TALES expression and the result
+ after evaluation will be used as the ETag header value.
+
- Other tokens will be added to the "Cache-control" HTTP response
header as follows:
@@ -116,6 +127,14 @@
'no_store=1' argument => "no-store" token
'must_revalidate=1' argument => "must-revalidate" token
+
+ 'proxy_revalidate=1' argument => "proxy-revalidate" token
+
+ 'public=1' argument => "public" token
+
+ 'private=1' argument => "private" token
+
+ 'no_transform=1' argument => "no-transform" token
"""
def __init__( self
@@ -127,6 +146,13 @@
, no_store=0
, must_revalidate=0
, vary=''
+ , etag_func=''
+ , s_max_age_secs=None
+ , proxy_revalidate=0
+ , public=0
+ , private=0
+ , no_transform=0
+ , enable_304s=0
):
if not predicate:
@@ -138,14 +164,24 @@
if max_age_secs is not None:
max_age_secs = int( max_age_secs )
+ if s_max_age_secs is not None:
+ s_max_age_secs = int( s_max_age_secs )
+
self._policy_id = policy_id
self._predicate = Expression( text=predicate )
self._mtime_func = Expression( text=mtime_func )
self._max_age_secs = max_age_secs
+ self._s_max_age_secs = s_max_age_secs
self._no_cache = int( no_cache )
self._no_store = int( no_store )
self._must_revalidate = int( must_revalidate )
+ self._proxy_revalidate = int( proxy_revalidate )
+ self._public = int( public )
+ self._private = int( private )
+ self._no_transform = int( no_transform )
self._vary = vary
+ self._etag_func = Expression( text=etag_func )
+ self._enable_304s = int ( enable_304s )
def getPolicyId( self ):
"""
@@ -167,6 +203,11 @@
"""
return self._max_age_secs
+ def getSMaxAgeSecs( self ):
+ """
+ """
+ return getattr(self, '_s_max_age_secs', None)
+
def getNoCache( self ):
"""
"""
@@ -182,11 +223,51 @@
"""
return self._must_revalidate
+ def getProxyRevalidate( self ):
+ """
+ """
+ return getattr(self, '_proxy_revalidate', 0)
+
+ def getPublic( self ):
+ """
+ """
+ return getattr(self, '_public', 0)
+
+ def getPrivate( self ):
+ """
+ """
+ return getattr(self, '_private', 0)
+
+ def getNoTransform( self ):
+ """
+ """
+ return getattr(self, '_no_transform', 0)
+
def getVary( self ):
"""
"""
return getattr(self, '_vary', '')
+ def getETagFunc( self ):
+ """
+ """
+ etag_func_text = ''
+ etag_func = getattr(self, '_etag_func', None)
+
+ if etag_func is not None:
+ etag_func_text = etag_func.text
+
+ return etag_func_text
+
+ def getEnable304s(self):
+ """
+ """
+ return getattr(self, '_enable_304s', 0)
+
+ def testPredicate(self, expr_context):
+ """ Does this request match our predicate?"""
+ return self._predicate(expr_context)
+
def getHeaders( self, expr_context ):
"""
Does this request match our predicate? If so, return a
@@ -195,7 +276,7 @@
"""
headers = []
- if self._predicate( expr_context ):
+ if self.testPredicate( expr_context ):
mtime = self._mtime_func( expr_context )
@@ -209,30 +290,50 @@
control = []
- if self._max_age_secs is not None:
+ if self.getMaxAgeSecs() is not None:
now = expr_context.vars[ 'time' ]
exp_time_str = rfc1123_date(now.timeTime() + self._max_age_secs)
headers.append( ( 'Expires', exp_time_str ) )
control.append( 'max-age=%d' % self._max_age_secs )
+
+ if self.getSMaxAgeSecs() is not None:
+ control.append( 's-maxage=%d' % self._s_max_age_secs )
- if self._no_cache:
+ if self.getNoCache():
control.append( 'no-cache' )
+ headers.append(('Pragma', 'no-cache')) # tell HTTP 1.0 clients not to cache
- if self._no_store:
+ if self.getNoStore():
control.append( 'no-store' )
- if self._must_revalidate:
+ if self.getPublic():
+ control.append( 'public' )
+
+ if self.getPrivate():
+ control.append( 'private' )
+
+ if self.getMustRevalidate():
control.append( 'must-revalidate' )
+ if self.getProxyRevalidate():
+ control.append( 'proxy-revalidate' )
+
+ if self.getNoTransform():
+ control.append( 'no-transform' )
+
if control:
headers.append( ( 'Cache-control', ', '.join( control ) ) )
if self.getVary():
headers.append( ( 'Vary', self._vary ) )
+ if self.getETagFunc():
+ headers.append( ( 'ETag', self._etag_func( expr_context ) ) )
+
return headers
+
class CachingPolicyManager( SimpleItem ):
"""
Manage the set of CachingPolicy objects for the site; dispatch
@@ -279,18 +380,30 @@
security.declareProtected( ManagePortal, 'addPolicy' )
def addPolicy( self
, policy_id
- , predicate # TALES expr (def. 'python:1')
- , mtime_func # TALES expr (def. 'content/modified')
- , max_age_secs # integer, seconds (def. 0)
- , no_cache # boolean (def. 0)
- , no_store # boolean (def. 0)
- , must_revalidate # boolean (def. 0)
- , vary
+ , predicate # TALES expr (def. 'python:1')
+ , mtime_func # TALES expr (def. 'object/modified')
+ , max_age_secs # integer, seconds (def. 0)
+ , no_cache # boolean (def. 0)
+ , no_store # boolean (def. 0)
+ , must_revalidate # boolean (def. 0)
+ , vary # string value
+ , etag_func # TALES expr (def. '')
, REQUEST=None
+ , s_max_age_secs=None # integer, seconds (def. None)
+ , proxy_revalidate=0 # boolean (def. 0)
+ , public=0 # boolean (def. 0)
+ , private=0 # boolean (def. 0)
+ , no_transform=0 # boolean (def. 0)
+ , enable_304s=0 # boolean (def. 0)
):
"""
Add a caching policy.
"""
+ if s_max_age_secs is None or str(s_max_age_secs).strip() == '':
+ s_max_age_secs = None
+ else:
+ s_max_age_secs = int(s_max_age_secs)
+
self._addPolicy( policy_id
, predicate
, mtime_func
@@ -299,6 +412,13 @@
, no_store
, must_revalidate
, vary
+ , etag_func
+ , s_max_age_secs
+ , proxy_revalidate
+ , public
+ , private
+ , no_transform
+ , enable_304s
)
if REQUEST is not None:
REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
@@ -310,18 +430,30 @@
security.declareProtected( ManagePortal, 'updatePolicy' )
def updatePolicy( self
, policy_id
- , predicate # TALES expr (def. 'python:1')
- , mtime_func # TALES expr (def. 'content/modified')
- , max_age_secs # integer, seconds
- , no_cache # boolean (def. 0)
- , no_store # boolean (def. 0)
- , must_revalidate # boolean (def. 0)
- , vary
+ , predicate # TALES expr (def. 'python:1')
+ , mtime_func # TALES expr (def. 'object/modified')
+ , max_age_secs # integer, seconds (def. 0)
+ , no_cache # boolean (def. 0)
+ , no_store # boolean (def. 0)
+ , must_revalidate # boolean (def. 0)
+ , vary # string value
+ , etag_func # TALES expr (def. '')
, REQUEST=None
+ , s_max_age_secs=None # integer, seconds (def. 0)
+ , proxy_revalidate=0 # boolean (def. 0)
+ , public=0 # boolean (def. 0)
+ , private=0 # boolean (def. 0)
+ , no_transform=0 # boolean (def. 0)
+ , enable_304s=0 # boolean (def. 0)
):
"""
Update a caching policy.
"""
+ if s_max_age_secs is None or str(s_max_age_secs).strip() == '':
+ s_max_age_secs = None
+ else:
+ s_max_age_secs = int(s_max_age_secs)
+
self._updatePolicy( policy_id
, predicate
, mtime_func
@@ -330,6 +462,13 @@
, no_store
, must_revalidate
, vary
+ , etag_func
+ , s_max_age_secs
+ , proxy_revalidate
+ , public
+ , private
+ , no_transform
+ , enable_304s
)
if REQUEST is not None:
REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
@@ -399,6 +538,13 @@
, no_store
, must_revalidate
, vary
+ , etag_func
+ , s_max_age_secs=None
+ , proxy_revalidate=0
+ , public=0
+ , private=0
+ , no_transform=0
+ , enable_304s=0
):
"""
Add a policy to our registry.
@@ -419,6 +565,13 @@
, no_store
, must_revalidate
, vary
+ , etag_func
+ , s_max_age_secs
+ , proxy_revalidate
+ , public
+ , private
+ , no_transform
+ , enable_304s
)
idlist = list( self._policy_ids )
idlist.append( policy_id )
@@ -434,6 +587,13 @@
, no_store
, must_revalidate
, vary
+ , etag_func
+ , s_max_age_secs=None
+ , proxy_revalidate=0
+ , public=0
+ , private=0
+ , no_transform=0
+ , enable_304s=0
):
"""
Update a policy in our registry.
@@ -449,6 +609,13 @@
, no_store
, must_revalidate
, vary
+ , etag_func
+ , s_max_age_secs
+ , proxy_revalidate
+ , public
+ , private
+ , no_transform
+ , enable_304s
)
security.declarePrivate( '_reorderPolicy' )
@@ -501,7 +668,30 @@
return ()
+ # 304 handling helper
+ security.declareProtected( View, 'getHTTPCachingHeaders' )
+ def getModTimeAndETag( self, content, view_method, keywords, time=None):
+ """ Return the modification time and ETag for the content object,
+ view method, and keywords as the tuple (modification_time, etag)
+ (where modification_time is a DateTime) or None.
+ """
+ context = createCPContext( content, view_method, keywords, time=time )
+ for policy_id, policy in self.listPolicies():
+ if policy.getEnable304s() and policy.testPredicate(context):
+ headers = policy.getHeaders(context)
+ if headers:
+ content_etag = None
+ content_mod_time = None
+ for key, value in headers:
+ lk = key.lower()
+ if lk == 'etag':
+ content_etag = value
+ elif lk == 'last-modified':
+ last_modified = DateTime(value)
+ return (last_modified, content_etag)
+ return None
+
InitializeClass( CachingPolicyManager )
def manage_addCachingPolicyManager( self, REQUEST=None ):
Modified: CMF/branches/sidnei-cachingpolicymanager-backport-branch/CMFCore/FSPageTemplate.py
===================================================================
--- CMF/branches/sidnei-cachingpolicymanager-backport-branch/CMFCore/FSPageTemplate.py 2005-09-09 02:26:08 UTC (rev 38412)
+++ CMF/branches/sidnei-cachingpolicymanager-backport-branch/CMFCore/FSPageTemplate.py 2005-09-09 02:33:38 UTC (rev 38413)
@@ -33,7 +33,7 @@
from CMFCorePermissions import View
from CMFCorePermissions import FTPAccess
from FSObject import FSObject
-from utils import _setCacheHeaders
+from utils import _setCacheHeaders, _checkConditionalGET
xml_detect_re = re.compile('^\s*<\?xml\s+')
@@ -120,6 +120,13 @@
def pt_render(self, source=0, extra_context={}):
self._updateFromFS() # Make sure the template has been loaded.
+
+ if not source:
+ # If we have a conditional get, set status 304 and return
+ # no content
+ if _checkConditionalGET(self, extra_context):
+ return ''
+
result = FSPageTemplate.inheritedAttribute('pt_render')(
self, source, extra_context
)
Modified: CMF/branches/sidnei-cachingpolicymanager-backport-branch/CMFCore/dtml/cachingPolicies.dtml
===================================================================
--- CMF/branches/sidnei-cachingpolicymanager-backport-branch/CMFCore/dtml/cachingPolicies.dtml 2005-09-09 02:26:08 UTC (rev 38412)
+++ CMF/branches/sidnei-cachingpolicymanager-backport-branch/CMFCore/dtml/cachingPolicies.dtml 2005-09-09 02:33:38 UTC (rev 38413)
@@ -18,12 +18,23 @@
<dtml-with policy>
<dtml-let nc_checked="getNoCache() and 'checked' or ''"
ns_checked="getNoStore() and 'checked' or ''"
- mr_checked="getMustRevalidate() and 'checked' or ''">
+ mr_checked="getMustRevalidate() and 'checked' or ''"
+ pr_checked="getProxyRevalidate() and 'checked' or ''"
+ pub_checked="getPublic() and 'checked' or ''"
+ priv_checked="getPrivate() and 'checked' or ''"
+ nt_checked="getNoTransform() and 'checked' or ''"
+ e304_checked="getEnable304s() and 'checked' or ''"
+ s_max_age_secs="getSMaxAgeSecs() is not None and getSMaxAgeSecs() or ''">
<input type="hidden" name="policy_id" value="&dtml-getPolicyId;">
<input type="hidden" name="no_cache:default:int" value="0">
<input type="hidden" name="no_store:default:int" value="0">
<input type="hidden" name="must_revalidate:default:int" value="0">
+ <input type="hidden" name="proxy_revalidate:default:int" value="0">
+ <input type="hidden" name="public:default:int" value="0">
+ <input type="hidden" name="private:default:int" value="0">
+ <input type="hidden" name="no_transform:default:int" value="0">
+ <input type="hidden" name="enable_304s:default:int" value="0">
<table>
@@ -82,16 +93,65 @@
</tr>
<tr valign="top">
+ <th align="right"> Max proxy cache age (secs) </th>
+ <td>
+ <input type="text"
+ name="s_max_age_secs"
+ value="&dtml-s_max_age_secs;">
+ </td>
+
+ <th align="right"> Proxy-revalidate? </th>
+ <td>
+ <input type="checkbox" name="proxy_revalidate:int"
+ value="1" &dtml-pr_checked;>
+ </td>
+ </tr>
+
+ <tr valign="top">
<th align="right"> Vary </th>
- <td colspan="3">
+ <td>
<input type="text"
name="vary"
value="&dtml-getVary;"
size="40">
</td>
+ <th align="right"> Public? </th>
+ <td>
+ <input type="checkbox" name="public:int"
+ value="1" &dtml-pub_checked;>
+ </td>
</tr>
<tr valign="top">
+ <th align="right"> ETag </th>
+ <td>
+ <input type="text"
+ name="etag_func"
+ value="&dtml-getETagFunc;"
+ size="40">
+ </td>
+ <th align="right"> Private? </th>
+ <td>
+ <input type="checkbox" name="private:int"
+ value="1" &dtml-priv_checked;>
+ </td>
+ </tr>
+
+ <tr valign="top">
+ <th align="right"> Enable 304s? </th>
+ <td>
+ <input type="checkbox" name="enable_304s:int"
+ value="1" &dtml-e304_checked;>
+ </td>
+ <th align="right"> No-transform? </th>
+ <td>
+ <input type="checkbox" name="no_transform:int"
+ value="1" &dtml-nt_checked;>
+ </td>
+ </tr>
+
+
+ <tr valign="top">
<td><br /></td>
<td colspan="3">
<input type="submit" name="updatePolicy:method" value=" Change ">
@@ -128,6 +188,11 @@
<input type="hidden" name="no_cache:default:int" value="0">
<input type="hidden" name="no_store:default:int" value="0">
<input type="hidden" name="must_revalidate:default:int" value="0">
+ <input type="hidden" name="proxy_revalidate:default:int" value="0">
+ <input type="hidden" name="public:default:int" value="0">
+ <input type="hidden" name="private:default:int" value="0">
+ <input type="hidden" name="no_transform:default:int" value="0">
+ <input type="hidden" name="enable_304s:default:int" value="0">
<table>
@@ -176,13 +241,58 @@
</tr>
<tr valign="top">
+ <th align="right"> Max proxy cache age (secs) </th>
+ <td>
+ <input type="text"
+ name="s_max_age_secs"
+ value="">
+ </td>
+
+ <th align="right"> Proxy-revalidate? </th>
+ <td>
+ <input type="checkbox" name="proxy_revalidate:int"
+ value="1">
+ </td>
+ </tr>
+
+ <tr valign="top">
<th align="right"> Vary </th>
- <td colspan="3">
+ <td>
<input type="text" name="vary" size="40">
</td>
+ <th align="right"> Public? </th>
+ <td>
+ <input type="checkbox" name="public:int"
+ value="1">
+ </td>
</tr>
<tr valign="top">
+ <th align="right"> ETag </th>
+ <td>
+ <input type="text" name="etag_func" size="40">
+ </td>
+ <th align="right"> Private? </th>
+ <td>
+ <input type="checkbox" name="private:int"
+ value="1">
+ </td>
+ </tr>
+
+ <tr valign="top">
+ <th align="right"> Enable 304s? </th>
+ <td>
+ <input type="checkbox" name="enable_304s:int"
+ value="1">
+ </td>
+ <th align="right"> No-transform? </th>
+ <td>
+ <input type="checkbox" name="no_transform:int"
+ value="1">
+ </td>
+ </tr>
+
+ <tr valign="top">
<td><br /></td>
<td>
<input type="submit" name="addPolicy:method" value=" Add ">
Property changes on: CMF/branches/sidnei-cachingpolicymanager-backport-branch/CMFCore/tests/fake_skins/fake_skin/testDTML.dtml
___________________________________________________________________
Name: svn:keywords
+ Author Date Id Revision
Property changes on: CMF/branches/sidnei-cachingpolicymanager-backport-branch/CMFCore/tests/fake_skins/fake_skin/testDTML.dtml.metadata
___________________________________________________________________
Name: svn:keywords
+ Author Date Id Revision
Copied: CMF/branches/sidnei-cachingpolicymanager-backport-branch/CMFCore/tests/framework.py (from rev 38380, CMF/branches/geoffd-cachingpolicymanager-branch/CMFCore/tests/framework.py)
===================================================================
--- CMF/branches/geoffd-cachingpolicymanager-branch/CMFCore/tests/framework.py 2005-09-08 01:48:07 UTC (rev 38380)
+++ CMF/branches/sidnei-cachingpolicymanager-backport-branch/CMFCore/tests/framework.py 2005-09-09 02:33:38 UTC (rev 38413)
@@ -0,0 +1,116 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""ZopeTestCase framework
+
+COPY THIS FILE TO YOUR 'tests' DIRECTORY.
+
+This version of framework.py will use the SOFTWARE_HOME
+environment variable to locate Zope and the Testing package.
+
+If the tests are run in an INSTANCE_HOME installation of Zope,
+Products.__path__ and sys.path with be adjusted to include the
+instance's Products and lib/python directories respectively.
+
+If you explicitly set INSTANCE_HOME prior to running the tests,
+auto-detection is disabled and the specified path will be used
+instead.
+
+If the 'tests' directory contains a custom_zodb.py file, INSTANCE_HOME
+will be adjusted to use it.
+
+If you set the ZEO_INSTANCE_HOME environment variable a ZEO setup
+is assumed, and you can attach to a running ZEO server (via the
+instance's custom_zodb.py).
+
+The following code should be at the top of every test module:
+
+ import os, sys
+ if __name__ == '__main__':
+ execfile(os.path.join(sys.path[0], 'framework.py'))
+
+...and the following at the bottom:
+
+ if __name__ == '__main__':
+ framework()
+
+$Id$
+"""
+
+__version__ = '0.2.4'
+
+# Save start state
+#
+__SOFTWARE_HOME = os.environ.get('SOFTWARE_HOME', '')
+__INSTANCE_HOME = os.environ.get('INSTANCE_HOME', '')
+
+if __SOFTWARE_HOME.endswith(os.sep):
+ __SOFTWARE_HOME = os.path.dirname(__SOFTWARE_HOME)
+
+if __INSTANCE_HOME.endswith(os.sep):
+ __INSTANCE_HOME = os.path.dirname(__INSTANCE_HOME)
+
+# Find and import the Testing package
+#
+if not sys.modules.has_key('Testing'):
+ p0 = sys.path[0]
+ if p0 and __name__ == '__main__':
+ os.chdir(p0)
+ p0 = ''
+ s = __SOFTWARE_HOME
+ p = d = s and s or os.getcwd()
+ while d:
+ if os.path.isdir(os.path.join(p, 'Testing')):
+ zope_home = os.path.dirname(os.path.dirname(p))
+ sys.path[:1] = [p0, p, zope_home]
+ break
+ p, d = s and ('','') or os.path.split(p)
+ else:
+ print 'Unable to locate Testing package.',
+ print 'You might need to set SOFTWARE_HOME.'
+ sys.exit(1)
+
+import Testing, unittest
+execfile(os.path.join(os.path.dirname(Testing.__file__), 'common.py'))
+
+# Include ZopeTestCase support
+#
+if 1: # Create a new scope
+
+ p = os.path.join(os.path.dirname(Testing.__file__), 'ZopeTestCase')
+
+ if not os.path.isdir(p):
+ print 'Unable to locate ZopeTestCase package.',
+ print 'You might need to install ZopeTestCase.'
+ sys.exit(1)
+
+ ztc_common = 'ztc_common.py'
+ ztc_common_global = os.path.join(p, ztc_common)
+
+ f = 0
+ if os.path.exists(ztc_common_global):
+ execfile(ztc_common_global)
+ f = 1
+ if os.path.exists(ztc_common):
+ execfile(ztc_common)
+ f = 1
+
+ if not f:
+ print 'Unable to locate %s.' % ztc_common
+ sys.exit(1)
+
+# Debug
+#
+print 'SOFTWARE_HOME: %s' % os.environ.get('SOFTWARE_HOME', 'Not set')
+print 'INSTANCE_HOME: %s' % os.environ.get('INSTANCE_HOME', 'Not set')
+sys.stdout.flush()
+
Property changes on: CMF/branches/sidnei-cachingpolicymanager-backport-branch/CMFCore/tests/framework.py
___________________________________________________________________
Name: svn:keywords
+ Author Date Id Revision
Name: svn:eol-style
+ native
Modified: CMF/branches/sidnei-cachingpolicymanager-backport-branch/CMFCore/tests/test_CachingPolicyManager.py
===================================================================
--- CMF/branches/sidnei-cachingpolicymanager-backport-branch/CMFCore/tests/test_CachingPolicyManager.py 2005-09-09 02:26:08 UTC (rev 38412)
+++ CMF/branches/sidnei-cachingpolicymanager-backport-branch/CMFCore/tests/test_CachingPolicyManager.py 2005-09-09 02:33:38 UTC (rev 38413)
@@ -160,19 +160,35 @@
self.assertEqual( headers[2][0].lower() , 'cache-control' )
self.assertEqual( headers[2][1] , 'max-age=86400' )
+ def test_sMaxAge( self ):
+
+ policy = self._makePolicy( 's_aged', s_max_age_secs=86400 )
+ context = self._makeContext()
+ headers = policy.getHeaders( context )
+
+ self.assertEqual( len( headers ), 2 )
+ self.assertEqual( headers[0][0].lower() , 'last-modified' )
+ self.assertEqual( headers[0][1]
+ , rfc1123_date(self._epoch.timeTime()) )
+ self.assertEqual( headers[1][0].lower() , 'cache-control' )
+ self.assertEqual( headers[1][1] , 's-maxage=86400' )
+ self.assertEqual(policy.getSMaxAgeSecs(), 86400)
+
def test_noCache( self ):
policy = self._makePolicy( 'noCache', no_cache=1 )
context = self._makeContext()
headers = policy.getHeaders( context )
- self.assertEqual( len( headers ), 2 )
+ self.assertEqual( len( headers ), 3 )
self.assertEqual( headers[0][0].lower() , 'last-modified' )
self.assertEqual( headers[0][1]
, rfc1123_date(self._epoch.timeTime()) )
- self.assertEqual( headers[1][0].lower() , 'cache-control' )
+ self.assertEqual( headers[1][0].lower() , 'pragma' )
self.assertEqual( headers[1][1] , 'no-cache' )
-
+ self.assertEqual( headers[2][0].lower() , 'cache-control' )
+ self.assertEqual( headers[2][1] , 'no-cache' )
+
def test_noStore( self ):
policy = self._makePolicy( 'noStore', no_store=1 )
@@ -198,10 +214,10 @@
, rfc1123_date(self._epoch.timeTime()) )
self.assertEqual( headers[1][0].lower() , 'cache-control' )
self.assertEqual( headers[1][1] , 'must-revalidate' )
-
- def test_combined( self ):
- policy = self._makePolicy( 'noStore', no_cache=1, no_store=1 )
+ def test_proxyRevalidate( self ):
+
+ policy = self._makePolicy( 'proxyRevalidate', proxy_revalidate=1 )
context = self._makeContext()
headers = policy.getHeaders( context )
@@ -210,9 +226,90 @@
self.assertEqual( headers[0][1]
, rfc1123_date(self._epoch.timeTime()) )
self.assertEqual( headers[1][0].lower() , 'cache-control' )
- self.assertEqual( headers[1][1] , 'no-cache, no-store' )
+ self.assertEqual( headers[1][1] , 'proxy-revalidate' )
+ self.assertEqual(policy.getProxyRevalidate(), 1)
+ def test_public( self ):
+ policy = self._makePolicy( 'public', public=1 )
+ context = self._makeContext()
+ headers = policy.getHeaders( context )
+
+ self.assertEqual( len( headers ), 2 )
+ self.assertEqual( headers[0][0].lower() , 'last-modified' )
+ self.assertEqual( headers[0][1]
+ , rfc1123_date(self._epoch.timeTime()) )
+ self.assertEqual( headers[1][0].lower() , 'cache-control' )
+ self.assertEqual( headers[1][1] , 'public' )
+ self.assertEqual(policy.getPublic(), 1)
+
+ def test_private( self ):
+
+ policy = self._makePolicy( 'private', private=1 )
+ context = self._makeContext()
+ headers = policy.getHeaders( context )
+
+ self.assertEqual( len( headers ), 2 )
+ self.assertEqual( headers[0][0].lower() , 'last-modified' )
+ self.assertEqual( headers[0][1]
+ , rfc1123_date(self._epoch.timeTime()) )
+ self.assertEqual( headers[1][0].lower() , 'cache-control' )
+ self.assertEqual( headers[1][1] , 'private' )
+ self.assertEqual(policy.getPrivate(), 1)
+
+ def test_noTransform( self ):
+
+ policy = self._makePolicy( 'noTransform', no_transform=1 )
+ context = self._makeContext()
+ headers = policy.getHeaders( context )
+
+ self.assertEqual( len( headers ), 2 )
+ self.assertEqual( headers[0][0].lower() , 'last-modified' )
+ self.assertEqual( headers[0][1]
+ , rfc1123_date(self._epoch.timeTime()) )
+ self.assertEqual( headers[1][0].lower() , 'cache-control' )
+ self.assertEqual( headers[1][1] , 'no-transform' )
+ self.assertEqual(policy.getNoTransform(), 1)
+
+ def test_ETag( self ):
+
+ # With an empty etag_func, no ETag should be produced
+ policy = self._makePolicy( 'ETag', etag_func='' )
+ context = self._makeContext()
+ headers = policy.getHeaders( context )
+
+ self.assertEqual( len( headers ), 1)
+ self.assertEqual( headers[0][0].lower() , 'last-modified' )
+ self.assertEqual( headers[0][1]
+ , rfc1123_date(self._epoch.timeTime()) )
+
+ policy = self._makePolicy( 'ETag', etag_func='string:foo' )
+ context = self._makeContext()
+ headers = policy.getHeaders( context )
+
+ self.assertEqual( len( headers ), 2)
+ self.assertEqual( headers[0][0].lower() , 'last-modified' )
+ self.assertEqual( headers[0][1]
+ , rfc1123_date(self._epoch.timeTime()) )
+ self.assertEqual( headers[1][0].lower(), 'etag' )
+ self.assertEqual( headers[1][1], 'foo' )
+
+ def test_combined( self ):
+
+ policy = self._makePolicy( 'noStore', no_cache=1, no_store=1 )
+ context = self._makeContext()
+ headers = policy.getHeaders( context )
+
+ self.assertEqual( len( headers ), 3 )
+ self.assertEqual( headers[0][0].lower() , 'last-modified' )
+ self.assertEqual( headers[0][1]
+ , rfc1123_date(self._epoch.timeTime()) )
+ self.assertEqual( headers[1][0].lower() , 'pragma' )
+ self.assertEqual( headers[1][1] , 'no-cache' )
+ self.assertEqual( headers[2][0].lower() , 'cache-control' )
+ self.assertEqual( headers[2][1] , 'no-cache, no-store' )
+
+
class CachingPolicyManagerTests( unittest.TestCase ):
def setUp(self):
@@ -251,28 +348,47 @@
self.assertEqual( len( headers ), 0 )
self.assertRaises( KeyError, mgr._updatePolicy
- , 'xyzzy', None, None, None, None, None, None, '' )
+ , 'xyzzy', None, None, None, None, None, None, '', '', None, None, None, None, None )
self.assertRaises( KeyError, mgr._removePolicy, 'xyzzy' )
self.assertRaises( KeyError, mgr._reorderPolicy, 'xyzzy', -1 )
- def test_addPolicy( self ):
+ def test_addAndUpdatePolicy( self ):
mgr = self._makeOne()
- mgr._addPolicy( 'first', 'python:1', None, 0, 0, 0, 0, '' )
- headers = mgr.getHTTPCachingHeaders( content=DummyContent(self._epoch)
- , view_method='foo_view'
- , keywords={}
- , time=self._epoch
- )
- self.assertEqual( len( headers ), 3 )
- self.assertEqual( headers[0][0].lower() , 'last-modified' )
- self.assertEqual( headers[0][1]
- , rfc1123_date(self._epoch.timeTime()) )
- self.assertEqual( headers[1][0].lower() , 'expires' )
- self.assertEqual( headers[1][1]
- , rfc1123_date(self._epoch.timeTime()) )
- self.assertEqual( headers[2][0].lower() , 'cache-control' )
- self.assertEqual( headers[2][1], 'max-age=0' )
+ mgr.addPolicy( 'first', 'python:1', 'mtime', 1, 0, 1, 0, 'vary',
+ 'etag', None, 2, 1, 0, 1, 0 )
+ p = mgr._policies['first']
+ self.assertEqual(p.getPolicyId(), 'first')
+ self.assertEqual(p.getPredicate(), 'python:1')
+ self.assertEqual(p.getMTimeFunc(), 'mtime')
+ self.assertEqual(p.getMaxAgeSecs(), 1)
+ self.assertEqual(p.getNoCache(), 0)
+ self.assertEqual(p.getNoStore(), 1)
+ self.assertEqual(p.getMustRevalidate(), 0)
+ self.assertEqual(p.getVary(), 'vary')
+ self.assertEqual(p.getETagFunc(), 'etag')
+ self.assertEqual(p.getSMaxAgeSecs(), 2)
+ self.assertEqual(p.getProxyRevalidate(), 1)
+ self.assertEqual(p.getPublic(), 0)
+ self.assertEqual(p.getPrivate(), 1)
+ self.assertEqual(p.getNoTransform(), 0)
+
+ mgr.updatePolicy( 'first', 'python:0', 'mtime2', 2, 1, 0, 1, 'vary2', 'etag2', None, 1, 0, 1, 0, 1 )
+ p = mgr._policies['first']
+ self.assertEqual(p.getPolicyId(), 'first')
+ self.assertEqual(p.getPredicate(), 'python:0')
+ self.assertEqual(p.getMTimeFunc(), 'mtime2')
+ self.assertEqual(p.getMaxAgeSecs(), 2)
+ self.assertEqual(p.getNoCache(), 1)
+ self.assertEqual(p.getNoStore(), 0)
+ self.assertEqual(p.getMustRevalidate(), 1)
+ self.assertEqual(p.getVary(), 'vary2')
+ self.assertEqual(p.getETagFunc(), 'etag2')
+ self.assertEqual(p.getSMaxAgeSecs(), 1)
+ self.assertEqual(p.getProxyRevalidate(), 0)
+ self.assertEqual(p.getPublic(), 1)
+ self.assertEqual(p.getPrivate(), 0)
+ self.assertEqual(p.getNoTransform(), 1)
def test_reorder( self ):
@@ -283,7 +399,7 @@
for policy_id in policy_ids:
mgr._addPolicy( policy_id
, 'python:"%s" in keywords.keys()' % policy_id
- , None, 0, 0, 0, 0, '' )
+ , None, 0, 0, 0, 0, '', '')
ids = tuple( map( lambda x: x[0], mgr.listPolicies() ) )
self.assertEqual( ids, policy_ids )
@@ -306,7 +422,7 @@
for policy_id, max_age_secs in policy_tuples:
mgr._addPolicy( policy_id
, 'python:"%s" in keywords.keys()' % policy_id
- , None, max_age_secs, 0, 0, 0, '' )
+ , None, max_age_secs, 0, 0, 0, '', '' )
return mgr
Copied: CMF/branches/sidnei-cachingpolicymanager-backport-branch/CMFCore/tests/test_Template304Handling.py (from rev 38380, CMF/branches/geoffd-cachingpolicymanager-branch/CMFCore/tests/test_Template304Handling.py)
===================================================================
--- CMF/branches/geoffd-cachingpolicymanager-branch/CMFCore/tests/test_Template304Handling.py 2005-09-08 01:48:07 UTC (rev 38380)
+++ CMF/branches/sidnei-cachingpolicymanager-backport-branch/CMFCore/tests/test_Template304Handling.py 2005-09-09 02:33:38 UTC (rev 38413)
@@ -0,0 +1,202 @@
+##############################################################################
+#
+# Copyright (c) 2002 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+""" Unit tests for status 304 handling by FSPageTemplate.
+
+$Id$
+"""
+
+import os, sys
+if __name__ == '__main__':
+ execfile(os.path.join(sys.path[0], 'framework.py'))
+
+try:
+ ZTC_available = True
+ from Testing import ZopeTestCase
+ from Testing.ZopeTestCase import Functional
+ from Testing.ZopeTestCase import PortalTestCase
+ from Testing.ZopeTestCase.PortalTestCase import portal_name
+except:
+ ZTC_available = False
+ class Functional:
+ pass
+ class PortalTestCase:
+ pass
+ portal_name = None
+
+if ZTC_available:
+ # set up the CMF
+ ZopeTestCase.installProduct('CMFCore')
+ ZopeTestCase.installProduct('CMFDefault')
+ ZopeTestCase.installProduct('ZCTextIndex')
+ ZopeTestCase.installProduct('CMFCalendar')
+ ZopeTestCase.installProduct('CMFTopic')
+ ZopeTestCase.installProduct('DCWorkflow')
+ ZopeTestCase.installProduct('MailHost')
+ ZopeTestCase.installProduct('PageTemplates')
+ ZopeTestCase.installProduct('PythonScripts')
+ ZopeTestCase.installProduct('ExternalMethod')
+
+from App.Common import rfc1123_date
+from DateTime import DateTime
+from AccessControl.SecurityManagement import newSecurityManager
+
+portal_owner = 'portal_owner'
+
+from Products.CMFCore import CachingPolicyManager
+
+
+class TestTemplate304Handling(Functional, PortalTestCase):
+
+ def setUp(self):
+ ZopeTestCase.PortalTestCase.setUp(self)
+
+ def getPortal(self):
+ # this is used by the framework to set things up
+ # do not call it directly -- use self.portal instead
+ if getattr(self.app, portal_name, None) is None:
+ self.app.manage_addProduct['CMFDefault'].manage_addCMFSite(portal_name)
+ return getattr(self.app, portal_name)
+
+ def afterSetUp(self):
+ uf = self.app.acl_users
+ password = 'secret'
+ uf.userFolderAddUser(portal_owner, password, ['Manager'], [])
+ user = uf.getUserById(portal_owner)
+ if not hasattr(user, 'aq_base'):
+ user = user.__of__(uf)
+ newSecurityManager(None, user)
+ self.owner_auth = '%s:%s' % (portal_owner, password)
+
+ self.portal.invokeFactory('Document', 'doc1')
+ self.portal.invokeFactory('Document', 'doc2')
+ self.portal.invokeFactory('Document', 'doc3')
+
+ CachingPolicyManager.manage_addCachingPolicyManager(self.portal)
+ cpm = self.portal.caching_policy_manager
+
+ cpm.addPolicy(policy_id = 'policy_no_etag',
+ predicate = 'python:object.getId()=="doc1"',
+ mtime_func = '',
+ max_age_secs = 0,
+ no_cache = 0,
+ no_store = 0,
+ must_revalidate = 0,
+ vary = '',
+ etag_func = '',
+ enable_304s = 1)
+
+ cpm.addPolicy(policy_id = 'policy_etag',
+ predicate = 'python:object.getId()=="doc2"',
+ mtime_func = '',
+ max_age_secs = 0,
+ no_cache = 0,
+ no_store = 0,
+ must_revalidate = 0,
+ vary = '',
+ etag_func = 'string:abc',
+ enable_304s = 1)
+
+ cpm.addPolicy(policy_id = 'policy_disabled',
+ predicate = 'python:object.getId()=="doc3"',
+ mtime_func = '',
+ max_age_secs = 0,
+ no_cache = 0,
+ no_store = 0,
+ must_revalidate = 0,
+ vary = '',
+ etag_func = 'string:abc',
+ enable_304s = 0)
+
+
+ def testUnconditionalGET(self):
+ content_path = '/' + self.portal.doc1.absolute_url(1) + '/document_view'
+ request = self.portal.REQUEST
+ response = self.publish(content_path, self.owner_auth)
+ self.assertEqual(response.getStatus(), 200)
+
+
+ def testConditionalGETNoETag(self):
+ yesterday = DateTime() - 1
+
+ doc1 = self.portal.doc1
+
+ content_path = '/' + doc1.absolute_url(1) + '/document_view'
+
+ response = self.publish(content_path, env={'IF_MODIFIED_SINCE': rfc1123_date(yesterday)}, basic=self.owner_auth)
+ self.assertEqual(response.getStatus(), 200)
+
+ response = self.publish(content_path, env={'IF_MODIFIED_SINCE': rfc1123_date(doc1.modified())}, basic=self.owner_auth)
+ self.assertEqual(response.getStatus(), 304)
+
+ response = self.publish(content_path, env={'IF_NONE_MATCH': '"123"' }, basic=self.owner_auth)
+ self.assertEqual(response.getStatus(), 200)
+
+ response = self.publish(content_path, env={'IF_MODIFIED_SINCE': rfc1123_date(doc1.modified()), 'IF_NONE_MATCH': '"123"'}, basic=self.owner_auth)
+ self.assertEqual(response.getStatus(), 200)
+
+
+ def testConditionalGETETag(self):
+ yesterday = DateTime() - 1
+
+ doc2 = self.portal.doc2
+
+ content_path = '/' + doc2.absolute_url(1) + '/document_view'
+ response = self.publish(content_path, env={'IF_MODIFIED_SINCE': rfc1123_date(yesterday)}, basic=self.owner_auth)
+ self.assertEqual(response.getStatus(), 200)
+
+ response = self.publish(content_path, env={'IF_MODIFIED_SINCE': rfc1123_date(doc2.modified())}, basic=self.owner_auth)
+ self.assertEqual(response.getStatus(), 200) # should be 200 because we also have an etag
+
+ response = self.publish(content_path, env={'IF_NONE_MATCH': '"123"' }, basic=self.owner_auth)
+ self.assertEqual(response.getStatus(), 200)
+
+ # etag matches, no modification time in request
+ response = self.publish(content_path, env={'IF_NONE_MATCH': '"abc"' }, basic=self.owner_auth)
+ self.assertEqual(response.getStatus(), 304)
+
+ response = self.publish(content_path, env={'IF_MODIFIED_SINCE': rfc1123_date(doc2.modified()), 'IF_NONE_MATCH': '"123"'}, basic=self.owner_auth)
+ self.assertEqual(response.getStatus(), 200)
+
+ response = self.publish(content_path, env={'IF_MODIFIED_SINCE': rfc1123_date(yesterday), 'IF_NONE_MATCH': '"abc"'}, basic=self.owner_auth)
+ self.assertEqual(response.getStatus(), 200)
+
+ # etag matches, valid modification time in request
+ response = self.publish(content_path, env={'IF_MODIFIED_SINCE': rfc1123_date(doc2.modified()), 'IF_NONE_MATCH': '"abc"'}, basic=self.owner_auth)
+ self.assertEqual(response.getStatus(), 304)
+
+
+ def testConditionalGETDisabled(self):
+ yesterday = DateTime() - 1
+
+ doc3 = self.portal.doc3
+ content_path = '/' + doc3.absolute_url(1) + '/document_view'
+
+ response = self.publish(content_path, env={'IF_NONE_MATCH': '"abc"' }, basic=self.owner_auth)
+ self.assertEqual(response.getStatus(), 200)
+
+ response = self.publish(content_path, env={'IF_MODIFIED_SINCE': rfc1123_date(doc3.modified()), 'IF_NONE_MATCH': '"abc"'}, basic=self.owner_auth)
+ self.assertEqual(response.getStatus(), 200)
+
+
+def test_suite():
+ if ZTC_available:
+ from unittest import TestSuite, makeSuite
+ suite = TestSuite()
+ suite.addTest(makeSuite(TestTemplate304Handling))
+ return suite
+ else:
+ print 'Warning: test_Template304Handling.py requires ZopeTestCase to run.'
+ print 'ZopeTestCase is available from http://www.zope.org/Members/shh/ZopeTestCase and ships with Zope 2.8+'
+
+if __name__ == '__main__':
+ framework()
Property changes on: CMF/branches/sidnei-cachingpolicymanager-backport-branch/CMFCore/tests/test_Template304Handling.py
___________________________________________________________________
Name: svn:keywords
+ Author Date Id Revision
Name: svn:eol-style
+ native
Modified: CMF/branches/sidnei-cachingpolicymanager-backport-branch/CMFCore/utils.py
===================================================================
--- CMF/branches/sidnei-cachingpolicymanager-backport-branch/CMFCore/utils.py 2005-09-09 02:26:08 UTC (rev 38412)
+++ CMF/branches/sidnei-cachingpolicymanager-backport-branch/CMFCore/utils.py 2005-09-09 02:33:38 UTC (rev 38413)
@@ -18,23 +18,26 @@
from types import StringType, UnicodeType
from copy import deepcopy
-from Globals import package_home
-from Globals import HTMLFile
-from Globals import ImageFile
-from Globals import InitializeClass
-from Globals import MessageDialog
-
-from ExtensionClass import Base
-from Acquisition import Implicit
-from Acquisition import aq_base, aq_get, aq_inner, aq_parent
-
from AccessControl import ClassSecurityInfo
from AccessControl import ModuleSecurityInfo
from AccessControl import getSecurityManager
from AccessControl.Permission import Permission
from AccessControl.PermissionRole import rolesForPermissionOn
from AccessControl.Role import gather_permissions
-
+from Acquisition import aq_base
+from Acquisition import aq_get
+from Acquisition import aq_inner
+from Acquisition import aq_parent
+from Acquisition import Implicit
+from DateTime import DateTime
+from ExtensionClass import Base
+from Globals import HTMLFile
+from Globals import ImageFile
+from Globals import InitializeClass
+from Globals import MessageDialog
+from Globals import package_home
+from OFS.misc_ import misc_ as misc_images
+from OFS.misc_ import Misc_ as MiscImage
from OFS.PropertyManager import PropertyManager
from OFS.SimpleItem import SimpleItem
from OFS.PropertySheets import PropertySheets
@@ -46,6 +49,7 @@
UNIQUE = 2
from Products.PageTemplates.Expressions import getEngine
from Products.PageTemplates.Expressions import SecureModuleImporter
+from thread import allocate_lock
security = ModuleSecurityInfo( 'Products.CMFCore.utils' )
@@ -307,10 +311,139 @@
something_changed = 1
return something_changed
+
+# Parse a string of etags from an If-None-Match header
+# Code follows ZPublisher.HTTPRequest.parse_cookie
+parse_etags_lock=allocate_lock()
+def parse_etags(text,
+ result=None,
+ etagre_quote = re.compile('(\s*\"([^\"]*)\"\s*,{0,1})'), # quoted etags (assumed separated by whitespace + a comma)
+ etagre_noquote = re.compile('(\s*([^,]*)\s*,{0,1})'), # non-quoted etags (assumed separated by whitespace + a comma)
+ acquire=parse_etags_lock.acquire,
+ release=parse_etags_lock.release,
+ ):
+
+ if result is None: result=[]
+ if not len(text):
+ return result
+
+ acquire()
+ try:
+ m = etagre_quote.match(text)
+ if m:
+ # Match quoted etag (spec-observing client)
+ l = len(m.group(1))
+ value = m.group(2)
+ else:
+ # Match non-quoted etag (lazy client)
+ m = etagre_noquote.match(text)
+ if m:
+ l = len(m.group(1))
+ value = m.group(2)
+ else:
+ return result
+ finally: release()
+
+ if value:
+ result.append(value)
+ return apply(parse_etags,(text[l:],result))
+
+
+def _checkConditionalGET(obj, extra_context):
+ """A conditional GET is done using one or both of the request
+ headers:
+
+ If-Modified-Since: Date
+ If-None-Match: list ETags (comma delimited, sometimes quoted)
+
+ If both conditions are present, both must be satisfied.
+
+ This method checks the caching policy manager to see if
+ a content object's Last-modified date and ETag satisfy
+ the conditional GET headers.
+
+ Returns the tuple (last_modified, etag) if the conditional
+ GET requirements are met and None if not.
+
+ It is possible for one of the tuple elements to be None.
+ For example, if there is no If-None-Match header and
+ the caching policy does not specify an ETag, we will
+ just return (last_modified, None).
+ """
+
+ REQUEST = getattr(obj, 'REQUEST', None)
+ if REQUEST is None:
+ return False
+
+ if_modified_since = REQUEST.get_header('If-Modified-Since', None)
+ if_none_match = REQUEST.get_header('If-None-Match', None)
+
+ if if_modified_since is None and if_none_match is None:
+ # not a conditional GET
+ return False
+
+ manager = getToolByName(obj, 'caching_policy_manager', None)
+ ret = manager.getModTimeAndETag(aq_parent(obj), obj.getId(), extra_context)
+ if ret is None:
+ # no appropriate policy or 304s not enabled
+ return False
+
+ (content_mod_time, content_etag) = ret
+ if content_mod_time:
+ mod_time_secs = content_mod_time.timeTime()
+ else:
+ mod_time_secs = None
+
+ if if_modified_since:
+ # from CMFCore/FSFile.py:
+ if_modified_since = if_modified_since.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:
+ if_modified_since=long(DateTime(if_modified_since).timeTime())
+ except:
+ if_mod_since=None
+
+ client_etags = None
+ if if_none_match:
+ client_etags = parse_etags(if_none_match)
+
+ if not if_modified_since and not client_etags:
+ # not a conditional GET, or headers are messed up
+ return False
+
+ if if_modified_since:
+ if not content_mod_time or mod_time_secs < 0 or mod_time_secs > if_modified_since:
+ return False
+
+ if client_etags:
+ if not content_etag or (content_etag not in client_etags and '*' not in client_etags):
+ return False
+ else:
+ # If we generate an ETag, don't validate the conditional GET unless the client supplies an ETag
+ # This may be more conservative than the spec requires, but we are already _way_ more conservative.
+ if content_etag:
+ return False
+
+ response = REQUEST.RESPONSE
+ if content_mod_time:
+ response.setHeader('Last-modified', str(content_mod_time))
+ if content_etag:
+ response.setHeader('ETag', content_etag, literal=1)
+ response.setStatus(304)
+
+ return True
+
+
security.declarePrivate('_setCacheHeaders')
def _setCacheHeaders(obj, extra_context):
"""Set cache headers according to cache policy manager for the obj."""
REQUEST = getattr(obj, 'REQUEST', None)
+
if REQUEST is not None:
content = aq_parent(obj)
manager = getToolByName(obj, 'caching_policy_manager', None)
More information about the CMF-checkins
mailing list