[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