此软件包与zope publisher集成,根据`If-None-Match`、`If-Match`、`If-Modified-Since`和`If-UnModifiedSince`协议验证条件请求。
项目描述
z3c.condtionalviews
z3c.conditionalviews是一个机制,用于根据一些条件协议(如实体标签或最后修改日期)验证HTTP请求。它也是可扩展的,因此像WebDAV这样的协议可以定义自己的条件协议,如IF头。
它通过将每个条件协议实现为一个IHTTPValidator实用工具来工作,请参阅etag和lastmodification模块以了解最常见的情况。然后当发布者调用某些视图时,我们查找这些实用工具,并要求它们根据实用工具实现的协议验证请求对象。
在调用视图时,以及在验证请求时,我们通常可以访问上下文、请求和视图本身。因此,IHTTPValidator实用工具通常将这些3个对象适配到实现特定于所讨论协议的接口的对象。例如,实体标签验证器查找实现IEtag的适配器。
Zope集成
>>> import zope.component >>> import zope.interface >>> import z3c.conditionalviews.interfaces >>> import z3c.conditionalviews.tests
装饰器
为了集成可缓存的通用浏览器视图,我们可以使用 z3c.conditionalviews.ConditionalView 对象装饰视图的调用方法。请注意,本测试中使用的所有视图都定义在 ftesting.zcml 文件中。
>>> response = http(r""" ... GET /@@simpleview.html HTTP/1.1 ... Host: localhost ... """, handle_errors = False) >>> response.getStatus() 200 >>> response.getHeader('content-length') '82' >>> response.getHeader('content-type') 'text/plain' >>> print response.getBody() xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
因为我们还没有定义实现 IETag 的适配器,所以响应中不包含 ETag 标头。
>>> response.getHeader('ETag') is None True
定义我们的 IETag 实现。
>>> class SimpleEtag(object): ... zope.interface.implements(z3c.conditionalviews.interfaces.IETag) ... def __init__(self, context, request, view): ... pass ... weak = False ... etag = "3d32b-211-bab57a40">>> zope.component.getGlobalSiteManager().registerAdapter( ... SimpleEtag, ... (zope.interface.Interface, ... zope.publisher.interfaces.browser.IBrowserRequest, ... zope.interface.Interface))>>> response = http(r""" ... GET /@@simpleview.html HTTP/1.1 ... Host: localhost ... """, handle_errors = False) >>> response.getStatus() 200 >>> response.getHeader('content-length') '82' >>> response.getHeader('content-type') 'text/plain' >>> response.getHeader('ETag') '"3d32b-211-bab57a40"' >>> print response.getBody() xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
现在通过设置请求头 If-None-Match: “3d32b-211-bab57a40”,我们的视图验证失败,并返回 304 响应。
>>> response = http(r""" ... GET /@@simpleview.html HTTP/1.1 ... Host: localhost ... If-None-Match: "3d32b-211-bab57a40" ... """, handle_errors = False) >>> response.getStatus() 304 >>> response.getHeader('ETag') '"3d32b-211-bab57a40"' >>> response.getBody() ''
XXX - 这看起来不正确,响应的内容长度和内容类型不应该为此响应设置。
>>> response.getHeader('content-length') '0' >>> response.getHeader('content-type') 'text/plain'
现在确保我们没有破坏发布者,确保我们仍然可以向不同的视图传递参数。
>>> response = http(r""" ... GET /@@simpleview.html?letter=y HTTP/1.1 ... Host: localhost ... """, handle_errors = False) >>> response.getStatus() 200 >>> response.getHeader('content-length') '82' >>> print response.getBody() yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
我们现在为这个请求得到一个 charset 值,因为 SimpleView 的默认值不是一个 unicode 字符串,而请求接收到的数据默认情况下会自动转换为 unicode。
>>> response.getHeader('content-type') 'text/plain;charset=utf-8'
由于请求中存在查询字符串,我们没有设置 ETag 标头。
>>> response.getHeader('ETag') is None True
以下请求中的查询字符串使请求有效,否则它将无效。
>>> response = http(r""" ... GET /@@simpleview.html?letter=y HTTP/1.1 ... If-None-Match: "3d32b-211-bab57a40" ... Host: localhost ... """, handle_errors = False) >>> response.getStatus() 200
通用 HTTP 条件发布
我们可以将验证方法与发布调用方法集成。这将对通过发布 callObject 方法传递的每个请求进行验证。这对于验证修改对象的请求很有用,以便客户端可以表示如果资源自上次下载以来没有改变,则可以修改此资源,或者如果请求 URI 指定的位置不存在现有资源。
这还有一个额外的好处,我们不必指定如何实现 PUT 方法。
>>> resp = http(r""" ... PUT /testfile HTTP/1.1 ... Authorization: Basic mgr:mgrpw ... Content-type: text/plain ... Content-length: 55 ... aaaaaaaaaa ... aaaaaaaaaa ... aaaaaaaaaa ... aaaaaaaaaa ... aaaaaaaaaa""", handle_errors = False) >>> resp.getStatus() 201 >>> resp.getHeader('Content-length') '0' >>> resp.getHeader('Location') 'http://localhost/testfile' >>> resp.getHeader('ETag', None) is None True
我们现在可以获取资源和实体标签。
>>> resp = http(r""" ... GET /testfile HTTP/1.1 ... Authorization: Basic mgr:mgrpw ... """) >>> resp.getStatus() 200 >>> resp.getHeader('ETag') '"testfile:1"' >>> print resp.getBody() aaaaaaaaaa aaaaaaaaaa aaaaaaaaaa aaaaaaaaaa aaaaaaaaaa
我们本来可以使用 HEAD 方法来获取实体标签。
>>> resp = http(r""" ... HEAD /testfile HTTP/1.1 ... Authorization: Basic mgr:mgrpw ... """) >>> resp.getStatus() 200 >>> resp.getHeader('ETag') '"testfile:1"'
没有 ‘If-None-Match’ 标头,我们将覆盖数据。
>>> resp = http(r""" ... PUT /testfile HTTP/1.1 ... Authorization: Basic mgr:mgrpw ... Content-type: text/plain ... Content-length: 55 ... bbbbbbbbbb ... bbbbbbbbbb ... bbbbbbbbbb ... bbbbbbbbbb ... bbbbbbbbbb""", handle_errors = False) >>> resp.getStatus() 200 >>> resp.getHeader('Content-length') '0' >>> resp.getHeader('Location', None) is None True >>> resp.getHeader('ETag') '"testfile:2"'>>> resp = http(r""" ... GET /testfile HTTP/1.1 ... Authorization: Basic mgr:mgrpw ... """) >>> resp.getStatus() 200 >>> print resp.getBody() bbbbbbbbbb bbbbbbbbbb bbbbbbbbbb bbbbbbbbbb bbbbbbbbbb
指定 If-None-Match: “*” 标头,表示只有在请求 URI 指定的位置不存在资源时才上传数据。如果该位置存在资源,则返回 412 Precondition Failed 响应,并且资源不会被修改。
>>> resp = http(r""" ... PUT /testfile HTTP/1.1 ... Authorization: Basic mgr:mgrpw ... If-None-Match: "*" ... Content-type: text/plain ... Content-length: 55 ... cccccccccc ... cccccccccc ... cccccccccc ... cccccccccc ... cccccccccc""") >>> resp.getStatus() 412 >>> resp.getHeader('Content-length') '0' >>> resp.getHeader('Location', None) is None True >>> resp.getHeader('ETag') '"testfile:2"'
文件没有更改。
>>> resp = http(r""" ... GET /testfile HTTP/1.1 ... Authorization: Basic mgr:mgrpw ... """) >>> resp.getStatus() 200 >>> print resp.getBody() bbbbbbbbbb bbbbbbbbbb bbbbbbbbbb bbbbbbbbbb bbbbbbbbbb
现在由于 testfile2 还不存在,我们包含内容。
>>> resp = http(r""" ... PUT /testfile2 HTTP/1.1 ... Authorization: Basic mgr:mgrpw ... If-None-Match: "*" ... Content-type: text/plain ... Content-length: 55 ... yyyyyyyyyy ... yyyyyyyyyy ... yyyyyyyyyy ... yyyyyyyyyy ... yyyyyyyyyy""") >>> resp.getStatus() 201 >>> resp.getHeader('Content-length') '0' >>> resp.getHeader('Location') 'http://localhost/testfile2' >>> resp.getHeader('ETag', None) is None # No etag adapter is configured True>>> resp = http(r""" ... GET /testfile2 HTTP/1.1 ... Authorization: Basic mgr:mgrpw ... """) >>> resp.getStatus() 200 >>> print resp.getBody() yyyyyyyyyy yyyyyyyyyy yyyyyyyyyy yyyyyyyyyy yyyyyyyyyy
我们现在可以删除资源,前提是它没有更改。所以对于 ‘/testfile’ 资源,我们可以使用其第一个实体标签来确认这一点。
>>> resp = http(r""" ... DELETE /testfile HTTP/1.1 ... Authorization: Basic mgr:mgrpw ... If-Match: "testfile:1" ... """) >>> resp.getStatus() 412
并且文件仍然存在。
>>> resp = http(r""" ... GET /testfile HTTP/1.1 ... Authorization: Basic mgr:mgrpw ... """) >>> resp.getStatus() 200
但是使用有效的实体标签我们可以删除资源。
>>> resp = http(r""" ... DELETE /testfile HTTP/1.1 ... Authorization: Basic mgr:mgrpw ... If-Match: "testfile:2" ... """) >>> resp.getStatus() 200 >>> resp.getBody() ''>>> resp = http(r""" ... GET /testfile HTTP/1.1 ... Authorization: Basic mgr:mgrpw ... """) >>> resp.getStatus() 404
方法不被允许
对于尚未注册的方法,我们仍然应该得到 405 Method Not Allowed 状态。
我们需要登录才能遍历到文件。
>>> resp = http(r""" ... FROG /testfile2 HTTP/1.1 ... Authorization: Basic mgr:mgrpw ... """) >>> resp.getStatus() 405 >>> resp.getHeader('ETag', None) is None True
清理
>>> zope.component.getGlobalSiteManager().unregisterAdapter( ... SimpleEtag, ... (zope.interface.Interface, ... zope.publisher.interfaces.browser.IBrowserRequest, ... zope.interface.Interface)) True
z3c.conditionalviews 的更改
1.0 (2008-09-27)
使用 IDCTimes 而不是 IZopeDublinCore,因为 IDCTimes 是实际所需的接口。
1.0b
修复了处理 If-Modified 协议时的时区问题。
0.9
初始发布。