跳转到主要内容

为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.

穿越时间

就像在真实浏览器中一样,你可以回到以前的状态。例如,我们现在正在查看 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>(&lt;InterfaceClass ...IAuthentication&gt;, 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>(&lt;InterfaceClass ...IAuthentication&gt;, 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 (41.4 kB 查看哈希值)

上传时间 源代码

支持者

AWS AWS 云计算和安全赞助商 Datadog Datadog 监控 Fastly Fastly CDN Google Google 下载分析 Microsoft Microsoft PSF 赞助商 Pingdom Pingdom 监控 Sentry Sentry 错误记录 StatusPage StatusPage 状态页面