为Zope 3应用程序提供REST框架
项目描述
此软件包提供了一种在Zope 3之上构建REST API的框架。
详细文档
在Zope 3中构建RESTful服务的框架
此软件包实现了几个与使用Zope发布者构建RESTful Web服务相关的组件。每个组件集都由相应的文本文件进行文档记录。
client.txt [必须阅读]
此软件包还提供了一个REST Web客户端,可用于测试或访问应用程序内的RESTful API。
null.txt [高级用户]
为了创建新资源,发布者必须能够遍历到尚未存在的资源/对象。此文件解释了这些空资源是如何工作的。
traverser.txt [高级用户]
traverser 模块包含几个遍历辅助组件,用于常见的遍历场景,例如容器和空资源。
rest.txt [信息性]
本文档介绍了在发布者中管理RESTful请求所需的钩子。它还讨论了这些组件是如何被发布者使用的。
REST客户端
REST客户端提供了一种简单的Python API,用于轻松与RESTful Web服务交互。它被设计成具有与Zope的测试浏览器相似的API。
让我们先实例化客户端。当然,我们有一个直接与Zope发布者通信的客户端版本
>>> from z3c.rest import testing >>> client = testing.RESTClient()
出于测试目的,我们为文件夹定义了一个简单的REST API。最简单的调用是检索根文件夹的内容
>>> client.open('https://127.0.0.1/') >>> print client.contents <?xml version="1.0" ?> <folder xmlns:xlink="http://www.w3.org/1999/xlink"> <name></name> <title></title> <items> </items> <acl xlink:type="simple" xlink:href="acl" xlink:title="ACL"/> </folder>
您也可以通过提供URL来实例化客户端
>>> client = testing.RESTClient('https://127.0.0.1/') >>> print client.contents <?xml version="1.0" ?> <folder xmlns:xlink="http://www.w3.org/1999/xlink"> <name></name> <title></title> <items> </items> <acl xlink:type="simple" xlink:href="acl" xlink:title="ACL"/> </folder>
也支持HTTPS URL
>>> client = testing.RESTClient('https://127.0.0.1/') Using SSL >>> print client.contents <?xml version="1.0" ?> <folder xmlns:xlink="http://www.w3.org/1999/xlink"> <name></name> <title></title> <items> </items> <acl xlink:type="simple" xlink:href="acl" xlink:title="ACL"/> </folder>
获取资源
方法 open() 隐式地使用了“GET” HTTP方法。另一种选择是使用这个
>>> client.get('https://127.0.0.1/') >>> print client.contents <?xml version="1.0" ?> <folder xmlns:xlink="http://www.w3.org/1999/xlink"> <name></name> <title></title> <items> </items> <acl xlink:type="simple" xlink:href="acl" xlink:title="ACL"/> </folder>
响应中还有其他一些信息可供使用
>>> client.url 'https://127.0.0.1/' >>> client.status 200 >>> client.reason 'Ok' >>> client.fullStatus '200 Ok' >>> client.headers [('X-Powered-By', 'Zope (www.zope.org), Python (www.python.org)'), ('Content-Length', '204'), ('Content-Type', 'text/xml;charset=utf-8')]
如果我们尝试访问一个不存在的资源,不会引发异常,但状态当然是“404”(未找到)
>>> client.get('https://127.0.0.1/unknown') >>> client.fullStatus '404 Not Found' >>> client.contents '' >>> client.headers [('X-Powered-By', 'Zope (www.zope.org), Python (www.python.org)'), ('Content-Length', '0')]
就像在原始测试浏览器中一样,我可以关闭Zope错误处理,Python异常将通过发布者传播
>>> client.handleErrors = False >>> client.get('https://127.0.0.1/unknown') Traceback (most recent call last): ... NotFound: Object: <zope.site.folder.Folder ...>, name: u'unknown'>>> client.handleErrors = True
由于RESTful API通常使用查询字符串键值对来参数化请求,因此这个REST客户端对此有很强的支持。例如,您可以直接在URL中指定参数
>>> client.get('https://127.0.0.1/?noitems=1') >>> print client.contents <?xml version="1.0" ?> <folder xmlns:xlink="http://www.w3.org/1999/xlink"> <name></name> <title></title> <acl xlink:type="simple" xlink:href="acl" xlink:title="ACL"/> </folder>
您也可以通过参数指定
>>> client.get('https://127.0.0.1/', params={'noitems': 1}) >>> print client.contents <?xml version="1.0" ?> <folder xmlns:xlink="http://www.w3.org/1999/xlink"> <name></name> <title></title> <acl xlink:type="simple" xlink:href="acl" xlink:title="ACL"/> </folder>
甚至可以将两种指定参数的方法结合起来
>>> client.get('https://127.0.0.1/?noitems=1', params={'notitle': 1}) >>> print client.contents <?xml version="1.0" ?> <folder xmlns:xlink="http://www.w3.org/1999/xlink"> <name></name> <acl xlink:type="simple" xlink:href="acl" xlink:title="ACL"/> </folder>
但我们的简易演示API还能做更多。参数也可以作为具有特殊前缀的标题来指定。标题可以全局指定,然后用于每个请求
>>> client.requestHeaders['demo-noitems'] = 'on' >>> client.get('https://127.0.0.1/') >>> print client.contents <?xml version="1.0" ?> <folder xmlns:xlink="http://www.w3.org/1999/xlink"> <name></name> <title></title> <acl xlink:type="simple" xlink:href="acl" xlink:title="ACL"/> </folder>
“open”方法还有一个“headers”参数,用于一次指定标题
>>> client.get('https://127.0.0.1/', headers={'demo-notitle': 1}) >>> print client.contents <?xml version="1.0" ?> <folder xmlns:xlink="http://www.w3.org/1999/xlink"> <name></name> <acl xlink:type="simple" xlink:href="acl" xlink:title="ACL"/> </folder>>>> del client.requestHeaders['demo-noitems']
最后,当处理实际网站时,可能会发生套接字错误。错误被传播,但错误号和消息被记录
>>> from z3c.rest.client import RESTClient >>> realClient = RESTClient() >>> realClient.open('https://127.0.0.1:65000') Traceback (most recent call last): ... error: (61, 'Connection refused')>>> realClient.fullStatus '61 Connection refused'
创建新资源
现在让我们在服务器根目录中创建一个新的资源。我们的简易样本应用程序将简单地创建另一个集合
>>> client.put( ... 'https://127.0.0.1/folder1', ... '''<?xml version="1.0" ?> ... <folder />''')>>> client.fullStatus '401 Unauthorized'
访问文件夹资源对每个人都是可用的。但如果你要修改任何资源,你必须登录
>>> client.setCredentials('globalmgr', 'globalmgrpw')
所以让我们再试一次
>>> client.put( ... 'https://127.0.0.1/folder1', ... '''<?xml version="1.0" ?> ... <folder />''')>>> client.fullStatus '201 Created' >>> client.headers [('X-Powered-By', 'Zope (www.zope.org), Python (www.python.org)'), ('Content-Length', '0'), ('Location', 'https://127.0.0.1/folder1')]
现在我们可以查看根容器并查看那里的条目
>>> client.get('https://127.0.0.1/') >>> print client.contents <?xml version="1.0" ?> <folder xmlns:xlink="http://www.w3.org/1999/xlink"> <name></name> <title></title> <items> <item xlink:type="simple" xlink:href="https://127.0.0.1/folder1" xlink:title="folder1"/> </items> <acl xlink:type="simple" xlink:href="acl" xlink:title="ACL"/> </folder>
顺便说一下,现在您可以使用相对URL来访问 folder1 资源
>>> client.get('folder1')>>> client.url 'https://127.0.0.1/folder1'>>> print client.contents <?xml version="1.0" ?> <folder xmlns:xlink="http://www.w3.org/1999/xlink"> <name>folder1</name> <title></title> <items> </items> <acl xlink:type="simple" xlink:href="acl" xlink:title="ACL"/> </folder>
当我们尝试在不存在资源之上创建资源时,我们会得到一个404错误
>>> client.put( ... 'https://127.0.0.1/folder2/folder21', ... '''<?xml version="1.0" ?> ... <folder />''')>>> client.fullStatus '404 Not Found'
修改资源
可以通过POST或PUT修改给定的资源,但它们有不同的语义。我们先看看POST。我们现在想更改文件夹的标题;可以这样完成
>>> client.post( ... 'https://127.0.0.1/folder1', ... '''<?xml version="1.0" ?> ... <folder> ... <title>My Folder 1</title> ... </folder>''')>>> client.fullStatus '200 Ok'>>> client.get() >>> print client.contents <?xml version="1.0" ?> <folder xmlns:xlink="http://www.w3.org/1999/xlink"> <name>folder1</name> <title>My Folder 1</title> <items> </items> <acl xlink:type="simple" xlink:href="acl" xlink:title="ACL"/> </folder>
如上所述,它也必须对PUT起作用
>>> client.put( ... 'https://127.0.0.1/folder1', ... '''<?xml version="1.0" ?> ... <folder> ... <title>Folder 1</title> ... </folder>''')>>> client.fullStatus '200 Ok'>>> client.get() >>> print client.contents <?xml version="1.0" ?> <folder xmlns:xlink="http://www.w3.org/1999/xlink"> <name>folder1</name> <title>Folder 1</title> <items> </items> <acl xlink:type="simple" xlink:href="acl" xlink:title="ACL"/> </folder>
删除资源
删除资源就像所有其他方法一样简单。让我们删除我们的 folder1
>>> client.delete('https://127.0.0.1/folder1')>>> client.fullStatus '200 Ok'
所以资源真的已经消失了
>>> client.get() >>> client.fullStatus '404 Not Found'
不应该能够删除不存在的资源
>>> client.delete('https://127.0.0.1/folder2') >>> client.fullStatus '404 Not Found'
此外,我们也不能删除根文件夹
>>> client.delete('https://127.0.0.1/') >>> client.fullStatus '405 Method Not Allowed'
搜索响应数据
虽然不是必需的,但大多数REST服务都是基于XML的。因此,客户端支持使用XPath检查结果XML。让我们创建几个文件夹,以便使其更有趣
>>> client.put( ... 'https://127.0.0.1/folder1', ... '''<?xml version="1.0" ?> ... <folder />''')>>> client.put( ... 'https://127.0.0.1/folder2', ... '''<?xml version="1.0" ?> ... <folder />''')
接下来我们获取根文件夹资源
>>> client.get('https://127.0.0.1/') >>> print client.contents <?xml version="1.0" ?> <folder xmlns:xlink="http://www.w3.org/1999/xlink"> <name></name> <title></title> <items> <item xlink:type="simple" xlink:href="https://127.0.0.1/folder1" xlink:title="folder1"/> <item xlink:type="simple" xlink:href="https://127.0.0.1/folder2" xlink:title="folder2"/> </items> <acl xlink:type="simple" xlink:href="acl" xlink:title="ACL"/> </folder>
但在一般情况下,检查字符串级别的XML输出是令人厌烦的。所以让我们写一个酷的XPath表达式来提取所有项目的xlink标题
>>> nsmap = {'xlink': "http://www.w3.org/1999/xlink"} >>> client.xpath('//folder/items/item/@xlink:title', nsmap) ['folder1', 'folder2']
然而,通常我们特别查询一个结果。在这些情况下,我们不想收到列表
>>> client.xpath('//folder/items/item[@xlink:title="folder1"]', nsmap, True) <Element item ...>
现在,如果我们检测到多个匹配项,即使我们只期望一个,那么将引发一个 ValueError
>>> client.xpath('//folder/items/item', nsmap, True) Traceback (most recent call last): ... ValueError: XPath expression returned more than one result.
访问链接
由于我们希望REST客户端的行为像浏览器一样——至少有一点——因此我们也可以使用 getLink() 方法来访问链接
>>> client.getLink('folder1') <XLink title='folder1' url='https://127.0.0.1/folder1'>
默认情况下,通过标题查找链接。但你也可以通过URL查找
>>> client.getLink(url='https://127.0.0.1/folder1') <XLink title='folder1' url='https://127.0.0.1/folder1'>
如果你忘记指定标题或URL,你会收到一个 ValueError
>>> client.getLink() Traceback (most recent call last): ... ValueError: You must specify a title or URL.
链接也可以是相对的,例如ACL的链接
>>> client.open('https://127.0.0.1/folder1') >>> client.getLink('ACL') <XLink title='ACL' url='https://127.0.0.1/folder1/acl'>>>> client.open('https://127.0.0.1/folder1/') >>> client.getLink('ACL') <XLink title='ACL' url='https://127.0.0.1/folder1/acl'>
链接的酷处在于你可以点击它
>>> client.open('https://127.0.0.1/') >>> client.url 'https://127.0.0.1/'>>> client.getLink('folder1').click()>>> client.url 'https://127.0.0.1/folder1'
穿越时间
就像在真实浏览器中一样,你可以回到以前的状态。例如,我们现在正在查看 folder1,…
>>> client.url 'https://127.0.0.1/folder1'
但是,如果我退一步,我又回到了根目录
>>> client.goBack()>>> client.url 'https://127.0.0.1/'>>> print client.contents <?xml version="1.0" ?> <folder xmlns:xlink="http://www.w3.org/1999/xlink"> <name></name> <title></title> <items> <item xlink:type="simple" xlink:href="https://127.0.0.1/folder1" xlink:title="folder1"/> <item xlink:type="simple" xlink:href="https://127.0.0.1/folder2" xlink:title="folder2"/> </items> <acl xlink:type="simple" xlink:href="acl" xlink:title="ACL"/> </folder>
但是,如果能够重新加载,回溯历史才酷。所以让我们删除 folder2
>>> client.getLink('folder2').click() >>> client.delete()
现在我们退回了2步
>>> client.goBack(2)>>> client.url 'https://127.0.0.1/'>>> print client.contents <?xml version="1.0" ?> <folder xmlns:xlink="http://www.w3.org/1999/xlink"> <name></name> <title></title> <items> <item xlink:type="simple" xlink:href="https://127.0.0.1/folder1" xlink:title="folder1"/> <item xlink:type="simple" xlink:href="https://127.0.0.1/folder2" xlink:title="folder2"/> </items> <acl xlink:type="simple" xlink:href="acl" xlink:title="ACL"/> </folder>
正如预期的那样,内容还没有变化。所以让我们重新加载
>>> client.reload()>>> client.url 'https://127.0.0.1/'>>> print client.contents <?xml version="1.0" ?> <folder xmlns:xlink="http://www.w3.org/1999/xlink"> <name></name> <title></title> <items> <item xlink:type="simple" xlink:href="https://127.0.0.1/folder1" xlink:title="folder1"/> </items> <acl xlink:type="simple" xlink:href="acl" xlink:title="ACL"/> </folder>
注意,退回0步不会做任何事情
>>> client.url 'https://127.0.0.1/'>>> client.getLink('folder1').click() >>> client.goBack(0)>>> client.url 'https://127.0.0.1/folder1'
此外,如果你试图退回到时间的起点,会引发一个值错误
>>> client.goBack(1000) Traceback (most recent call last): ... ValueError: There is not enough history.
绝对URL
如上所述,我们允许指定相对URL,已经进行了使用绝对URL的调用。使用名为 absoluteURL() 的函数来计算新的绝对URL。
>>> from z3c.rest.client import absoluteURL
基本功能很简单
>>> absoluteURL('https://127.0.0.1/folder1/', 'folder11') 'https://127.0.0.1/folder1/folder11'
它还检测新的URL部分是否为绝对URL,如果是,则替换原始的基本URL
>>> absoluteURL('http://einstein/folder1', 'https://127.0.0.1/folder11') 'https://127.0.0.1/folder11'
如果基本URL没有尾部斜杠,它会自动添加
>>> absoluteURL('https://127.0.0.1/folder1', 'folder11') 'https://127.0.0.1/folder1/folder11'
新URL末尾的任何斜杠都得到保留
>>> absoluteURL('https://127.0.0.1/folder1', 'folder11/') 'https://127.0.0.1/folder1/folder11/'>>> absoluteURL('http://einstein/folder1', 'https://127.0.0.1/folder11/') 'https://127.0.0.1/folder11/'
该函数还正确处理包含查询字符串的更复杂URL
>>> absoluteURL('https://127.0.0.1/folder1', 'folder11?max=1') 'https://127.0.0.1/folder1/folder11?max=1'>>> absoluteURL('https://127.0.0.1/folder1', 'folder11/?max=1') 'https://127.0.0.1/folder1/folder11/?max=1'
如果基本URL包含查询字符串,结果URL也将包含
>>> absoluteURL('https://127.0.0.1/folder1?max=1', 'folder11/') 'https://127.0.0.1/folder1/folder11/?max=1'>>> absoluteURL('https://127.0.0.1/folder1/?max=1', 'folder11/') 'https://127.0.0.1/folder1/folder11/?max=1'>>> absoluteURL('https://127.0.0.1/folder1?max=1', 'folder11') 'https://127.0.0.1/folder1/folder11?max=1'>>> absoluteURL('https://127.0.0.1/folder1/?max=1', 'folder11') 'https://127.0.0.1/folder1/folder11?max=1'
如果基本URL和相对URL都提供了查询字符串,它们将合并
>>> absoluteURL('https://127.0.0.1/folder1/?max=1', 'folder11?min=0') 'https://127.0.0.1/folder1/folder11?max=1&min=0'>>> absoluteURL('https://127.0.0.1/folder1/?max=1', 'folder11?min=0&start=0') 'https://127.0.0.1/folder1/folder11?max=1&min=0&start=0'>>> absoluteURL('https://127.0.0.1/folder1/?max=1&start=0', 'folder11?min=0') 'https://127.0.0.1/folder1/folder11?max=1&start=0&min=0'
空资源
有时需要遍历尚不存在的资源。特别是,在创建使用“PUT”或“POST”的资源时,这是必需的。遍历器的责任是正确处理这些情况并生成空资源。本文件仅描述其行为。
可以使用容器和资源的名称轻松实例化空资源
>>> class Folder(object): ... __parent__ = __name__ = None ... child = None ... ... def __init__(self, name=''): ... self.name = self.__name__ = name ... ... def __repr__(self): ... return '<Folder %r>' %self.name >>> folder = Folder()>>> from z3c.rest import null >>> resource = null.NullResource(folder, 'resource')>>> from zope.app.http.interfaces import INullResource >>> INullResource.providedBy(resource) True
空资源是位置,因此具有安全性
>>> from zope.location.interfaces import ILocation >>> ILocation.providedBy(resource) True
容器也是父级
>>> resource.container <Folder ''> >>> resource.__parent__ <Folder ''>
资源名称可在以下位置获得
>>> resource.name 'resource' >>> resource.__name__ 'resource'
对于空资源,有一个特殊的“PUT”实现。它是通过查找容器中名为“NullPUT”的视图来工作的。这样,可以使用一个空资源实现来使用所有容器实现。
>>> import StringIO >>> from z3c.rest import rest >>> request = rest.RESTRequest(StringIO.StringIO(), {})>>> nullPut = null.NullPUT(resource, request) >>> nullPut.PUT()
由于我们的 Folder 类中没有名为“NullPUT”的视图,我们得到了一个501返回状态
>>> request.response.getStatusString() '501 Not Implemented'
现在让我们注册一个简单的NullPUT视图
>>> class FolderAPI(rest.RESTView): ... ... def NullPUT(self, resource): ... self.context.child = Folder(resource.name) ... self.context.child.__parent__ = self.context ... return self.context.child>>> import zope.component >>> from z3c.rest import interfaces >>> zope.component.provideAdapter( ... FolderAPI, (Folder, interfaces.IRESTRequest), name='NullPUT')
让我们确保我们的位置结构设置正确,以便绝对URL可以正常工作
>>> from zope.traversing.interfaces import IContainmentRoot >>> import zope.interface >>> zope.interface.alsoProvides(folder, IContainmentRoot)
现在我们准备PUT新的资源
>>> request = rest.RESTRequest( ... StringIO.StringIO(), {'SERVER_URL': 'https://127.0.0.1/'})>>> nullPut = null.NullPUT(resource, request) >>> nullPut.PUT()>>> request.response.getStatusString() '201 Created' >>> request.response.getHeader('Location') 'https://127.0.0.1/resource'>>> folder.child <Folder 'resource'>
REST遍历器组件
能够控制和扩展遍历对于任何RESTful API都是必不可少的。此包使用 z3c.traverser 包的可插拔遍历器实现,以提供灵活的遍历机制。
REST可插拔遍历器
REST可插拔遍历器已注册为所有组件类型。其实现已在 z3c.traverser 包中进行了全面测试。
>>> from z3c.rest import traverser>>> import StringIO >>> from z3c.rest import rest >>> request = rest.RESTRequest(StringIO.StringIO(), {})>>> pluggable = traverser.RESTPluggableTraverser(object(), request) >>> pluggable <z3c.rest.traverser.RESTPluggableTraverser object at ...>
项目容器遍历器插件
项目映射接口——项目容器从中继承——是Python中最小映射接口。因此,一旦实现了通过此项目容器的遍历,它就可以由所有其他容器接口和实现使用。
让我们先创建一个非常简单的项目容器实现
>>> import zope.interface >>> from zope.app.container.interfaces import IItemContainer>>> class SimpleContainer(dict): ... zope.interface.implements(IItemContainer) ... def __init__(self, name=''): ... self.name = name ... def __repr__(self): ... return '<Container name=%s>' %self.name>>> container = SimpleContainer()>>> container['sub1'] = SimpleContainer('sub1') >>> container['sub2'] = SimpleContainer('sub2')
创建遍历器插件实例后,
>>> request = rest.RESTRequest(StringIO.StringIO(), {})>>> containerTraverser = traverser.ContainerItemTraverserPlugin( ... container, request)
我们可以遍历到该容器的子对象
>>> containerTraverser.publishTraverse(request, 'sub1') <Container name=sub1>
如果没有找到适当的子项,可能会发生一些有趣的事情。在正常情况下,将引发 NotFound
>>> containerTraverser.publishTraverse(request, 'unknown') Traceback (most recent call last): ... NotFound: Object: <Container name=>, name: 'unknown'
然而,如果请求是PUT请求,我们必须生成一个空资源
>>> request.method = 'PUT' >>> containerTraverser.publishTraverse(request, 'unknown') <NullResource 'unknown'>
然而,只有当前资源是遍历堆栈中的最后一个资源时,才会创建空资源
>>> request.setTraversalStack(('sub11',)) >>> containerTraverser.publishTraverse(request, 'unknown') Traceback (most recent call last): ... NotFound: Object: <Container name=>, name: 'unknown'
就是这样。
REST请求的发布钩子
阅读此文件需要——在一定程度上——读者熟悉出版过程的基本步骤。
发布请求工厂
当WSGI服务器发送请求环境和响应初始化调用到Zope WSGI发布器应用程序_[1]时,Zope发布过程开始。WSGI发布器应用程序负责在发布器中处理请求并输出结果。
为了在发布器中处理请求,我们必须创建一个有效的发布器请求对象。WSGI发布器应用程序使用一个请求工厂来实现这一目的。这个包实现了这个工厂,以确保始终创建一个特殊的REST请求(基于HTTP请求)。
请求工厂使用ZODB数据库对象实例化。
>>> from ZODB.DB import DB >>> from ZODB.DemoStorage import DemoStorage >>> db = DB(DemoStorage())
现在让我们创建这个工厂
>>> from z3c.rest import rest >>> RequestFactory = rest.RESTPublicationRequestFactory(db)
当服务器发送请求时,请求的创建如下
>>> import StringIO >>> inStream = StringIO.StringIO('Input stream')>>> env = {'HTTP_ACCEPT_LANGUAGE': 'en-US,en', ... 'SERVER_URL': 'https://127.0.0.1:8080/'}>>> request = RequestFactory(inStream, env)
现在我们得到了一个有效的请求,我们可以通过发布器发送它
>>> request <z3c.rest.rest.RESTRequest instance URL=https://127.0.0.1:8080>
然而,请求只负责在发布器中代表网络请求,并没有直接了解应用程序。但请求连接到一个特定应用程序的组件,在这个例子中是Zope 3的发布组件。
>>> request.publication <zope.app.publication.http.HTTPPublication object at ...>
由于我们不需要特殊的REST发布,我们只是重新使用了更通用的HTTP版本。对于所有请求,发布内容都将相同。它还包含对数据库的引用。
>>> request.publication.db <ZODB.DB.DB object at ...>
不幸的是,要成功通过发布器发送请求,需要更多的设置。发布需要许多其他发布方面可用,包括遍历、安全和正确构建的数据库。然而,我们仍然可以看到失败。
>>> from zope.publisher import publish >>> publish.publish(request) <z3c.rest.rest.RESTRequest instance URL=https://127.0.0.1:8080> >>> print request.response.consumeBody() <?xml version="1.0" ?> <error> <name>ComponentLookupError</name> <explanation>(<InterfaceClass ...IAuthentication>, u'')</explanation> </error>
让我们稍微回顾一下。最初,我们开始时是想创建一个使用REST请求内部使用的发布器WSGI应用程序实例。你只需要做以下事情
>>> from zope.app import wsgi >>> app = wsgi.WSGIPublisherApplication( ... db, rest.RESTPublicationRequestFactory) >>> app <zope.app.wsgi.WSGIPublisherApplication object at ...>
当WSGI服务器向WSGI应用程序发送请求时,发生以下情况
>>> status = None >>> headers = None >>> def start_response(s, h): ... global status ... global headers ... status, headers = s, h>>> wsgiEnv = {'wsgi.input': inStream} >>> wsgiEnv.update(env)>>> print '\n'.join(app(wsgiEnv, start_response)) <?xml version="1.0" ?> <error> <name>ComponentLookupError</name> <explanation>(<InterfaceClass ...IAuthentication>, u'')</explanation> </error>
REST请求
对于大多数部分,REST请求与HTTP请求相同,所以我不会过多介绍HTTP请求API。
REST请求主要在HTTP请求的基础上扩展,它将URL的查询字符串解析成一系列参数。这发生在 processInputs() 期间。
如果没有查询字符串,参数映射为空
>>> request = RequestFactory( ... StringIO.StringIO(), {}) >>> request.processInputs() >>> request.parameters {}
现在让我们传递一些参数
>>> request = RequestFactory( ... StringIO.StringIO(), ... {'QUERY_STRING': 'format=html&action=delete&item=1&item=3'}) >>> request.processInputs() >>> pprint(request.parameters) {'action': 'delete', 'format': 'html', 'item': ['1', '3']}
我们还覆盖了一些请求的映射方法,以便参数和环境值可以作为请求的一部分使用
>>> sorted(request.keys()) ['QUERY_STRING', 'action', 'format', 'item']>>> request.get('QUERY_STRING') 'format=html&action=delete&item=1&item=3' >>> request.get('action') 'delete' >>> request.get('unknwon', 'default') 'default'
REST响应
REST响应几乎与HTTP响应相同,只是其异常处理程序返回XML而不是HTML。然而,这种方法只用于经典和字符串异常。
从响应开始...
>>> response = rest.RESTResponse()
...我们可以现在调用处理程序
>>> class MyException(Exception): ... pass>>> response.handleException((MyException, MyException(), None)) >>> response._status 500 >>> response._reason 'Internal Server Error' >>> print '\n'.join(response._result) <?xml version="1.0" ?> <error> <name>MyException</name> <explanation></explanation> </error>
让我们也尝试一个字符串异常
>>> response.handleException(('StringException', 'Some details', None)) >>> response._status 500 >>> response._reason 'Internal Server Error' >>> print '\n'.join(response._result) <?xml version="1.0" ?> <error> <name>StringException</name> <explanation>Some details</explanation> </error>
Redirect异常是特殊的。它实际上会导致请求被重定向。
>>> response._request = rest.RESTRequest(None, {})>>> from zope.publisher.interfaces import Redirect >>> response.handleException( ... (Redirect, Redirect('https://127.0.0.1'), None), trusted=True) >>> response._status 302 >>> response._reason 'Moved Temporarily' >>> response._headers['location'] ['https://127.0.0.1']
REST视图
与浏览器视图不同,REST视图不表示其自己的子资源(例如“index.html”)。相反,它只定义了特定内容组件的HTTP方法的操作。
这里有一个例子
>>> class ObjectAPI(rest.RESTView): ... ... def GET(self): ... return str(self.context)
RESTView基类提供了一个合适的构造函数
>>> class Object(object): ... def __repr__(self): ... return '<Object>' >>> myobj = Object()>>> request = RequestFactory( ... StringIO.StringIO(), {'SERVER_URL': 'https://127.0.0.1:8080/myobj'})>>> view = ObjectAPI(myobj, request)
当发布器遍历到myobj时,它将根据HTTP方法(如“GET”)查找视图。然后它还期望找到同名的方法,并调用它 _[2]。
>>> view.GET() '<Object>'
REST视图,像所有其他视图一样,公开了其上下文和请求
>>> view.context <Object> >>> view.request <z3c.rest.rest.RESTRequest instance URL=https://127.0.0.1:8080/myobj>
此外,视图必须被定位,因此它有一个父视图
>>> view.__parent__ <Object>
当然,你可以将其设置为其他内容
>>> view.__parent__ = 1 >>> view.__parent__ 1
更改
0.4.0 (2010-10-05)
添加了对未声明但必需的安装依赖项 zope.app.http 的支持。
0.3.0 (2010-10-05)
添加了未声明的测试依赖项。
更新了测试设置,并修复了与ZTK 1.0兼容的测试。
使用Python的doctest模块,而不是已废弃的zope.testing.doctest。
0.2.5 (2008-09-16)
错误/缺陷:最终实现了按预期处理URL合并。还添加了广泛的测试来记录行为。
0.2.4 (2008-09-04)
RESTClient()现在正确解析https:// URLs。
0.2.3 (2008-03-20)
错误/缺陷:唉,正确处理尾随斜杠变得非常痛苦。我真心希望我已经按照REST客户端应该的方式完成了它。
0.2.2 (2008-03-19)
错误/缺陷:客户端总是将斜杠添加到URL的末尾。但一些REST API对此非常敏感。现在,如果存在,则保留斜杠,否则不添加任何内容。
0.2.1 (2008-03-06)
错误:有时没有读取响应体,客户端的内容为空。不幸的是,这个问题无法可靠地重现,但调试显示连接过早关闭。(Roy Mathew)
特性:使该软件包与Python 2.4和2.5兼容。
特性:为z3c.rest要求lxml 2.0。
0.2.0 (2008-03-03)
特性:使HTTP调用器对REST客户端可插拔,允许使用除RESTRequest之外的其他请求类型。
0.1.0 (2008-03-03)
初始发布
发布者钩子以构建专用REST服务器
错误视图支持
基于z3c.traverser的可插拔REST遍历器
REST客户端
最小示例应用程序
项目详情
z3c.rest-0.4.0.tar.gz的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 08015b3cce2b933cf67efe7c800b8fd7762552bc898e9c00566757faeb9418aa |
|
MD5 | bae65e621c87bd3e6db30bccaa3762bd |
|
BLAKE2b-256 | 7a003e8c672038746b4a42e043603fb7576b530360cf3009aaf63f294c01259a |