[Checkins] SVN: zope.browserresource/branches/mgedmin-etag-support/ ETag generation can be customized or disabled by providing an IETag

Marius Gedminas marius at pov.lt
Fri Aug 13 10:47:55 EDT 2010


Log message for revision 115666:
  ETag generation can be customized or disabled by providing an IETag
  multi-adapter on (IFileResource, your-application-skin).
  
  

Changed:
  U   zope.browserresource/branches/mgedmin-etag-support/CHANGES.txt
  U   zope.browserresource/branches/mgedmin-etag-support/src/zope/browserresource/configure.zcml
  U   zope.browserresource/branches/mgedmin-etag-support/src/zope/browserresource/file.py
  U   zope.browserresource/branches/mgedmin-etag-support/src/zope/browserresource/interfaces.py
  U   zope.browserresource/branches/mgedmin-etag-support/src/zope/browserresource/tests/test_file.py
  U   zope.browserresource/branches/mgedmin-etag-support/src/zope/browserresource/tests/test_i18nfile.py

-=-
Modified: zope.browserresource/branches/mgedmin-etag-support/CHANGES.txt
===================================================================
--- zope.browserresource/branches/mgedmin-etag-support/CHANGES.txt	2010-08-13 09:13:22 UTC (rev 115665)
+++ zope.browserresource/branches/mgedmin-etag-support/CHANGES.txt	2010-08-13 14:47:52 UTC (rev 115666)
@@ -5,7 +5,9 @@
 3.11.0 (unreleased)
 ===================
 
-- Support the HTTP Etag header for file resources.
+- Support the HTTP ETag header for file resources.  ETag generation can be
+  customized or disabled by providing an IETag multi-adapter on
+  (IFileResource, your-application-skin).
 
 3.10.3 (2010-04-30)
 ===================

Modified: zope.browserresource/branches/mgedmin-etag-support/src/zope/browserresource/configure.zcml
===================================================================
--- zope.browserresource/branches/mgedmin-etag-support/src/zope/browserresource/configure.zcml	2010-08-13 09:13:22 UTC (rev 115665)
+++ zope.browserresource/branches/mgedmin-etag-support/src/zope/browserresource/configure.zcml	2010-08-13 14:47:52 UTC (rev 115666)
@@ -1,7 +1,8 @@
 <configure xmlns="http://namespaces.zope.org/zope">
 
   <adapter factory=".resource.AbsoluteURL" />
-  
+  <adapter factory=".file.FileETag" />
+
   <view
       for="zope.component.interfaces.ISite"
       type="zope.publisher.interfaces.browser.IDefaultBrowserLayer"
@@ -17,14 +18,14 @@
         attributes="GET HEAD __call__"
         />
   </class>
-  
+
   <class class=".i18nfile.I18nFileResource">
     <allow
         interface="zope.publisher.interfaces.browser.IBrowserPublisher"
         attributes="GET HEAD __call__"
         />
   </class>
-  
+
   <class class=".directory.DirectoryResource">
     <allow
         interface="zope.publisher.interfaces.browser.IBrowserPublisher"

Modified: zope.browserresource/branches/mgedmin-etag-support/src/zope/browserresource/file.py
===================================================================
--- zope.browserresource/branches/mgedmin-etag-support/src/zope/browserresource/file.py	2010-08-13 09:13:22 UTC (rev 115665)
+++ zope.browserresource/branches/mgedmin-etag-support/src/zope/browserresource/file.py	2010-08-13 14:47:52 UTC (rev 115666)
@@ -24,11 +24,15 @@
 
 from zope.contenttype import guess_content_type
 from zope.interface import implements, classProvides
+from zope.component import adapts, getMultiAdapter
 from zope.publisher.browser import BrowserView
 from zope.publisher.interfaces import NotFound
+from zope.publisher.interfaces.browser import IBrowserRequest
 from zope.publisher.interfaces.browser import IBrowserPublisher
 
 from zope.browserresource.resource import Resource
+from zope.browserresource.interfaces import IETag
+from zope.browserresource.interfaces import IFileResource
 from zope.browserresource.interfaces import IResourceFactory
 from zope.browserresource.interfaces import IResourceFactoryFactory
 
@@ -116,18 +120,17 @@
         self.path = path
         self.__name__ = name
         f = open(path, 'rb')
-        data = f.read()
+        self.data = f.read()
         f.close()
-        self.content_type = guess_content_type(path, data)[0]
+        self.content_type = guess_content_type(path, self.data)[0]
 
         self.lmt = float(os.path.getmtime(path)) or time.time()
         self.lmh = formatdate(self.lmt, usegmt=True)
-        self.etag = '%s-%s' % (self.lmt, len(data))
 
 
 class FileResource(BrowserView, Resource):
 
-    implements(IBrowserPublisher)
+    implements(IFileResource, IBrowserPublisher)
 
     cacheTimeout = 86400
 
@@ -185,7 +188,7 @@
           >>> factory = FileResourceFactory(testFilePath, nullChecker, 'test.txt')
           >>> request = TestRequest()
           >>> resource = factory(request)
-          >>> resource.GET() ==  open(testFilePath, 'rb').read()
+          >>> resource.GET() == open(testFilePath, 'rb').read()
           True
           >>> request.response.getHeader('Content-Type') == 'text/plain'
           True
@@ -196,6 +199,8 @@
         request = self.request
         response = request.response
 
+        etag = getMultiAdapter((self, request), IETag)(file.lmt, file.data)
+
         setCacheControl(response, self.cacheTimeout)
 
         can_return_304 = False
@@ -229,15 +234,14 @@
         header = request.getHeader('If-None-Match', None)
         if header is not None:
             can_return_304 = True
-            etag = getattr(file, 'etag', None)
             tags = parse_etags(header)
             if not etag or not etag_matches(quote_etag(etag), tags):
                 all_cache_checks_passed = False
 
         # 304 responses MUST contain ETag, if one would've been sent with
         # a 200 response
-        if file.etag:
-            response.setHeader('ETag', quote_etag(file.etag))
+        if etag:
+            response.setHeader('ETag', quote_etag(etag))
 
         if can_return_304 and all_cache_checks_passed:
             response.setStatus(304)
@@ -250,12 +254,8 @@
         response.setHeader('Content-Type', file.content_type)
         response.setHeader('Last-Modified', file.lmh)
 
-        f = open(file.path,'rb')
-        data = f.read()
-        f.close()
+        return file.data
 
-        return data
-
     def HEAD(self):
         '''Return proper headers and no content for HEAD requests
 
@@ -269,11 +269,12 @@
 
         '''
         file = self.chooseContext()
+        etag = getMultiAdapter((self, self.request), IETag)(file.lmt, file.data)
         response = self.request.response
         response.setHeader('Content-Type', file.content_type)
         response.setHeader('Last-Modified', file.lmh)
-        if file.etag:
-            response.setHeader('ETag', file.etag)
+        if etag:
+            response.setHeader('ETag', etag)
         setCacheControl(response, self.cacheTimeout)
         return ''
 
@@ -285,6 +286,19 @@
         return data
 
 
+class FileETag(object):
+
+    adapts(IFileResource, IBrowserRequest)
+    implements(IETag)
+
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+
+    def __call__(self, mtime, content):
+        return '%s-%s' % (mtime, len(content))
+
+
 def setCacheControl(response, secs=86400):
     # Cache for one day by default
     response.setHeader('Cache-Control', 'public,max-age=%s' % secs)

Modified: zope.browserresource/branches/mgedmin-etag-support/src/zope/browserresource/interfaces.py
===================================================================
--- zope.browserresource/branches/mgedmin-etag-support/src/zope/browserresource/interfaces.py	2010-08-13 09:13:22 UTC (rev 115665)
+++ zope.browserresource/branches/mgedmin-etag-support/src/zope/browserresource/interfaces.py	2010-08-13 14:47:52 UTC (rev 115666)
@@ -23,22 +23,35 @@
     def __call__():
         """return the absolute URL of this resource."""
 
+class IFileResource(IResource):
+    pass
+
 class IResourceFactory(Interface):
-    
+
     def __call__(request):
         """Return an IResource object"""
 
 class IResourceFactoryFactory(Interface):
     """A factory for IResourceFactory objects
-    
+
     These factories are registered as named utilities that can be selected
     for creating resource factories in a pluggable way.
-    
+
     Resource directories and browser:resource directive use these utilities
     to choose what resource to create, depending on the file extension, so
     third-party packages could easily plug-in additional resource types.
-    
+
     """
-    
+
     def __call__(path, checker, name):
         """Return an IResourceFactory"""
+
+class IETag(Interface):
+    """An adapter for computing resource ETags."""
+
+    def __call__(mtime, content):
+        """Compute an ETag for a resource.
+
+        May return None to disable the ETag header.
+        """
+

Modified: zope.browserresource/branches/mgedmin-etag-support/src/zope/browserresource/tests/test_file.py
===================================================================
--- zope.browserresource/branches/mgedmin-etag-support/src/zope/browserresource/tests/test_file.py	2010-08-13 09:13:22 UTC (rev 115665)
+++ zope.browserresource/branches/mgedmin-etag-support/src/zope/browserresource/tests/test_file.py	2010-08-13 14:47:52 UTC (rev 115666)
@@ -25,11 +25,38 @@
 
 from zope.testing import cleanup
 from zope.publisher.browser import TestRequest
+from zope.publisher.interfaces.browser import IBrowserRequest
 from zope.security.checker import NamesChecker
+from zope.component import provideAdapter, adapts
+from zope.interface import implements
+from zope.interface.verify import verifyObject
 
-from zope.browserresource.file import FileResourceFactory
+from zope.browserresource.file import FileResourceFactory, FileETag
+from zope.browserresource.interfaces import IFileResource, IETag
 
 
+class MyETag(object):
+    adapts(IFileResource, IBrowserRequest)
+    implements(IETag)
+
+    def __init__(self, context, request):
+        pass
+
+    def __call__(self, mtime, content):
+        return 'myetag'
+
+
+class NoETag(object):
+    adapts(IFileResource, IBrowserRequest)
+    implements(IETag)
+
+    def __init__(self, context, request):
+        pass
+
+    def __call__(self, mtime, content):
+        return None
+
+
 def setUp(test):
     cleanup.setUp()
     data_dir = os.path.join(os.path.dirname(__file__), 'testfiles')
@@ -37,12 +64,28 @@
     test.globs['testFilePath'] = os.path.join(data_dir, 'test.txt')
     test.globs['nullChecker'] = NamesChecker()
     test.globs['TestRequest'] = TestRequest
+    provideAdapter(MyETag)
 
 
 def tearDown(test):
     cleanup.tearDown()
 
 
+def doctest_FileETag():
+    """Tests for FileETag
+
+        >>> etag_maker = FileETag(object(), TestRequest())
+        >>> verifyObject(IETag, etag_maker)
+        True
+
+    By default we constuct an ETag from the file's mtime and size
+
+        >>> etag_maker(1234, 'abc')
+        '1234-3'
+
+    """
+
+
 def doctest_FileResource_GET_sets_cache_headers():
     """Test caching headers set by FileResource.GET
 
@@ -53,7 +96,6 @@
         >>> file = factory._FileResourceFactory__file # get mangled file
         >>> file.lmt = timestamp
         >>> file.lmh = formatdate(timestamp, usegmt=True)
-        >>> file.etag = 'myetag'
 
         >>> request = TestRequest()
         >>> resource = factory(request)
@@ -82,7 +124,6 @@
         >>> file = factory._FileResourceFactory__file # get mangled file
         >>> file.lmt = timestamp
         >>> file.lmh = formatdate(timestamp, usegmt=True)
-        >>> file.etag = 'myetag'
 
         >>> before = timestamp - 1000
         >>> request = TestRequest(HTTP_IF_MODIFIED_SINCE=formatdate(before, usegmt=True))
@@ -141,7 +182,6 @@
         >>> file = factory._FileResourceFactory__file # get mangled file
         >>> file.lmt = timestamp
         >>> file.lmh = formatdate(timestamp, usegmt=True)
-        >>> file.etag = 'myetag'
 
         >>> request = TestRequest(HTTP_IF_NONE_MATCH='"othertag"')
         >>> resource = factory(request)
@@ -178,7 +218,7 @@
 
     it also won't fail if we don't have an etag for the resource
 
-        >>> file.etag = None
+        >>> provideAdapter(NoETag)
         >>> request = TestRequest(HTTP_IF_NONE_MATCH='"someetag"')
         >>> resource = factory(request)
         >>> bool(resource.GET())
@@ -197,7 +237,6 @@
         >>> file = factory._FileResourceFactory__file # get mangled file
         >>> file.lmt = timestamp
         >>> file.lmh = formatdate(timestamp, usegmt=True)
-        >>> file.etag = 'myetag'
 
     We've a match
 
@@ -238,6 +277,7 @@
 
     """
 
+
 def test_suite():
     return unittest.TestSuite((
         doctest.DocTestSuite(

Modified: zope.browserresource/branches/mgedmin-etag-support/src/zope/browserresource/tests/test_i18nfile.py
===================================================================
--- zope.browserresource/branches/mgedmin-etag-support/src/zope/browserresource/tests/test_i18nfile.py	2010-08-13 09:13:22 UTC (rev 115665)
+++ zope.browserresource/branches/mgedmin-etag-support/src/zope/browserresource/tests/test_i18nfile.py	2010-08-13 14:47:52 UTC (rev 115666)
@@ -18,14 +18,17 @@
 
 from zope.publisher.interfaces import NotFound
 
-from zope.component import provideAdapter, provideUtility
+from zope.interface import implements
+from zope.component import provideAdapter, provideUtility, adapts
 from zope.testing import cleanup
 
 from zope.i18n.interfaces import IUserPreferredCharsets, IUserPreferredLanguages
 
 from zope.publisher.http import IHTTPRequest, HTTPCharsets
 from zope.publisher.browser import BrowserLanguages, TestRequest
+from zope.publisher.interfaces.browser import IBrowserRequest
 
+from zope.browserresource.interfaces import IFileResource, IETag
 from zope.browserresource.i18nfile import I18nFileResource
 from zope.browserresource.i18nfile import I18nFileResourceFactory
 from zope.browserresource.file import File
@@ -39,6 +42,17 @@
 test_directory = os.path.dirname(p.__file__)
 
 
+class MyETag(object):
+    adapts(IFileResource, IBrowserRequest)
+    implements(IETag)
+
+    def __init__(self, context, request):
+        pass
+
+    def __call__(self, mtime, content):
+        return 'myetag'
+
+
 class Test(cleanup.CleanUp, TestII18nAware):
 
     def setUp(self):
@@ -48,8 +62,8 @@
         provideAdapter(BrowserLanguages, (IHTTPRequest,), IUserPreferredLanguages)
         # Setup the negotiator utility
         provideUtility(negotiator, INegotiator)
+        provideAdapter(MyETag)
 
-
     def _createObject(self):
         obj = I18nFileResource({'en':None, 'lt':None, 'fr':None},
                                TestRequest(), 'fr')



More information about the checkins mailing list