跳转到主要内容

Zope3的JSON RPC服务器和客户端实现

项目描述

此包为Zope3提供了JSON-RPC服务器实现。

详细文档

JSONRPC

JSON是javascript对象表示法。JSON-RPC执行与XML-RPC相同的服务,但传输协议是JSON而不是XML。

非常感谢Jim Washington对zif.jsonserver的工作。此项目使用了Jim编写的大量代码。我实现了一个额外的python JSONRPC代理,可以与服务器通信。这意味着我们可以使用这个库从python调用python。JSON-RPC代理使用了类似于XML-RPC实现的模式。

还有一个额外的xmlhttp和jsonjavascript实现,为JavaScript提供了JSON-RPC代理实现。

此项目提供了建议的请求类型“application/json”。只要未正式弃用,就支持请求类型“application/json-rpc”。

此项目的目标是提供一个JSON-RPC实现。此包不支持处理带有BrowserRequest的简单浏览器视图。我还不知道这是否好或不好,以及我将如何发展此包。

我的部分目标目前是这样的,但如果有朝一日我理解了围绕JSON的所有概念,比如JSPON、JSONP、CrossSite等,它们可能会在未来改变。

  • 提供一种安全的方式来处理客户端到服务器的JSON调用。我希望我们能在某天实现JSONRequest。CrossSite似乎使用了一个有趣的概念

  • 简单的Python实现

  • 与JQuery(见http://www.jquery.org)一起使用。

  • 除了JQuery和基本zope包之外,没有其他依赖。

  • 经过良好测试(目前JavaScript不是这种情况)

关于JSON

有关JSON的更多信息,请访问www.json.org。

有关JSON 1.1规范的更多信息,请访问http://json-rpc.org/wd/JSON-RPC-1-1-WD-20060807.html

此包无法做什么

JSON和此包有不同的限制。目前此包无法处理以下任务

  • 处理文件上传

  • 处理GET请求

请注意,JSONRPCRequest实现基于IHTTPRequest,这意味着如果在Python中调用它们,则没有其他浏览器页面可用,例如getMultiAdapter((context, request), name='myViewName')。这是明确这样做的。如果您想在JSON请求/调用中使用来自这些浏览器页面的内容,可以继承您的皮肤以IJSONRPCLayer和IBrowserRequest,并为该自定义层注册您的JSON-RPC视图。

JSON-RPC服务器

JSON服务器查找内容类型“application/json”,并将这些请求作为JSON-RPC处理。JSON的官方MIME类型是“application/json”。也支持旧的内容类型application/json-rpc

让我们定义一个内容对象

>>> import zope.interface
>>> class IDemoContent(zope.interface.Interface):
...     """Demo content interface."""
>>> import persistent
>>> class DemoContent(persistent.Persistent):
...     """Demo content."""
...     zope.interface.implements(IDemoContent)

并定义一个JSONRPC方法视图

>>> from z3c.jsonrpc import publisher
>>> class DemoView(publisher.MethodPublisher):
...     """Sample JSON view."""
...
...     def hello(self):
...         return u"Hello World"
...
...     def greeting(self, name):
...         return u"Hello %s" % name
...
...     def mixedparams(self, prefix, bar=None, foo=None):
...         # Note; keyword arguments can be found in request.form
...         return u"%s %s %s" % (prefix, bar, foo)
...
...     def kws(self, adam=None, foo=None, bar=None):
...         # Note; keyword arguments can be found in request.form
...         a = self.request.get('adam')
...         b = self.request.form.get('foo')
...         c = self.request.form.get('bar')
...         return u"%s %s %s" % (a, b, c)
...
...     def showId(self):
...         return u"The json id is: %s" % self.request.jsonId
...
...     def forceValueError(self):
...         raise ValueError('Something was wrong in server method.')

让我们定义一个内容对象,它是一个容器

>>> import zope.interface
>>> class IDemoContainer(zope.container.interfaces.IReadContainer):
...     """Demo container interface."""
>>> import persistent
>>> from zope.container import btree
>>> class DemoContainer(btree.BTreeContainer):
...     """Demo container."""
...     zope.interface.implements(IDemoContainer)

并定义一个JSONRPC方法视图

>>> from z3c.jsonrpc import publisher
>>> class DemoContainerView(publisher.MethodPublisher):
...     """Sample JSON view."""
...
...     def available(self):
...         return u"Hello World"
...
...     def greeting(self, name):
...         return u"Hello %s" % name
...
...     def mixedparams(self, prefix, foo=None, bar=None):
...         # Note; keyword arguments can be found in request.form
...         return u"%s %s %s" % (prefix, foo, bar)
...
...     def kws(self, adam=None, foo=None, bar=None):
...         # Note; keyword arguments can be found in request.form
...         a = self.request.get('adam')
...         b = self.request.form.get('foo')
...         c = self.request.form.get('bar')
...         return u"%s %s %s" % (a, b, c)
...
...     def showId(self):
...         return u"The json id is: %s" % self.request.jsonId
...
...     def forceValueError(self):
...         raise ValueError('Something was wrong in server method.')

在假包jsonsamples下使其可用

>>> import sys
>>> sys.modules['custom'] = type('Module', (), {})()
>>> sys.modules['custom'].IDemoContent = IDemoContent
>>> sys.modules['custom'].DemoContent = DemoContent
>>> sys.modules['custom'].DemoView = DemoView
>>> sys.modules['custom'].IDemoContainer = IDemoContainer
>>> sys.modules['custom'].DemoContainer = DemoContainer
>>> sys.modules['custom'].DemoContainerView = DemoContainerView

让我们展示如何注册jsonrpc视图

>>> from zope.configuration import xmlconfig
>>> import z3c.jsonrpc
>>> context = xmlconfig.file('meta.zcml', z3c.jsonrpc)
>>> context = xmlconfig.string("""
... <configure
...     xmlns:z3c="http://namespaces.zope.org/z3c">
...   <z3c:jsonrpc
...       for="custom.IDemoContent"
...       class="custom.DemoView"
...       permission="zope.Public"
...       methods="hello greeting mixedparams kws showId forceValueError"
...       layer="z3c.jsonrpc.testing.IJSONRPCTestSkin"
...       />
... </configure>
... """, context)

让我们展示如何为容器注册jsonrpc视图:(容器类也需要权限配置)

>>> context = xmlconfig.file('meta.zcml', z3c.jsonrpc)
>>> context = xmlconfig.file('meta.zcml', zope.security, context)
>>> context = xmlconfig.string("""
... <configure
...     xmlns:z3c="http://namespaces.zope.org/z3c"
...     xmlns="http://namespaces.zope.org/zope">
...     <class class="custom.DemoContainer">
...       <allow
...           interface="custom.IDemoContainer"
...           />
...     </class>
...   <z3c:jsonrpc
...       for="custom.IDemoContainer"
...       class="custom.DemoContainerView"
...       permission="zope.Public"
...       methods="available greeting mixedparams kws showId forceValueError"
...       layer="z3c.jsonrpc.testing.IJSONRPCTestSkin"
...       />
... </configure>
... """, context)

现在我们将在我们的站点中设置一个内容对象

>>> site  = getRootFolder()
>>> content = DemoContent()
>>> site['content'] = content
>>> container = DemoContainer()
>>> site['container'] = container

现在我们可以从我们的JSONRPC视图中调用方法

>>> from z3c.jsonrpc import testing
>>> request = testing.TestRequest()
>>> demoView = DemoView(content, request)
>>> demoView.hello()
u'Hello World'

但这不是很直观。让我们看看如何使用遍历器遍历到方法hello

>>> from z3c.jsonrpc.publisher import MethodTraverser
>>> methodTraverser = MethodTraverser(demoView, request)
>>> methodTraverser.publishTraverse(request, 'hello')()
u'Hello World'

现在我们尝试使用测试浏览器访问JSON-RPC视图方法。如您所见,没有可访问的视图。这是因为JSONRPC视图不是浏览器视图,也不可遍历。错误表明请求工厂回退到浏览器请求工厂

>>> from zope.testbrowser.testing import Browser
>>> browser = Browser()
>>> browser.handleErrors = False
>>> browser.addHeader('Accept-Language', 'en')
>>> browser.addHeader('Content-Type', 'application/json')
>>> siteURL = 'https://127.0.0.1/++skin++JSONRPCTestSkin'
>>> browser.open(siteURL + '/content/hello')
Traceback (most recent call last):
...
NotFound: Object: <zope.site.folder.Folder...: u'++skin++JSONRPCTestSkin'

测试

如果您需要测试JSONRPC视图,您可以在“JSON-RPC代理”部分中按以下示例使用测试代理。

JSON-RPC代理

jsonrpc包还提供了一个JSON-RPC代理实现。此实现类似于xmlrpclib中已知的实现,但它可以处理JSON而不是XML。

让我们尝试调用我们之前定义的名为hello的方法

>>> from z3c.jsonrpc.testing import JSONRPCTestProxy
>>> proxy = JSONRPCTestProxy(siteURL + '/content')
>>> proxy.hello()
u'Hello World'

根据jsonrpc规范,也可以省略params,我们需要通过直接使用POST来测试这一点,因为测试代理始终设置params。

>>> browser.post(siteURL + '/content', "{'method':'hello', 'id':1}",
...              content_type='application/json')
>>> browser.contents
'{"jsonrpc":"2.0","result":"Hello World","id":1}'
>>> browser.post(siteURL + '/content', "{'method':'hello', 'params':null, 'id':1}",
...              content_type='application/json')
>>> browser.contents
'{"jsonrpc":"2.0","result":"Hello World","id":1}'
>>> proxy2 = JSONRPCTestProxy(siteURL + '/container')
>>> proxy2.available()
u'Hello World'

现在让我们使用一个参数进行远程过程调用

>>> proxy.greeting(u'Jessy')
u'Hello Jessy'

让我们调用命名参数

>>> proxy.kws(bar=u'BAR', foo=u'FOO')
u'None FOO BAR'

在JSON响应中还有一个id。让我们在我们的JSONRPCProxy中使用这样的json请求id

>>> proxy = JSONRPCTestProxy(siteURL + '/content', jsonId = u'my id')
>>> proxy.showId()
u'The json id is: my id'

代理也认识这个id作为jsonId

>>> proxy.jsonId
u'my id'

JSON-RPC版本

让我们从版本1.0开始测试不同的JSON-RPC版本

>>> v1 = JSONRPCTestProxy(siteURL + '/container', jsonVersion='1.0')
>>> v1.available()
u'Hello World'
>>> v1.greeting(u'Jessy')
u'Hello Jessy'
>>> v1.kws(bar=u'BAR', foo=u'FOO')
u'None FOO BAR'
>>> v1 = JSONRPCTestProxy(siteURL + '/content', jsonId = u'my id',
...     jsonVersion='1.0')
>>> v1.showId()
u'The json id is: my id'
>>> v1.jsonId
u'my id'

现在使用JSON-RPC版本1.1进行测试

>>> v11 = JSONRPCTestProxy(siteURL + '/container', jsonVersion='1.1')
>>> v11.available()
u'Hello World'
>>> v11.greeting(u'Jessy')
u'Hello Jessy'
>>> v11.kws(bar=u'BAR', foo=u'FOO')
u'None FOO BAR'
>>> v11 = JSONRPCTestProxy(siteURL + '/content', jsonId = u'my id',
...     jsonVersion='1.1')
>>> v11.showId()
u'The json id is: my id'
>>> v11.jsonId
u'my id'

现在使用JSON-RPC版本2.0进行测试

>>> v2 = JSONRPCTestProxy(siteURL + '/container', jsonVersion='2.0')
>>> v2.available()
u'Hello World'
>>> v2.greeting(u'Jessy')
u'Hello Jessy'
>>> v2.kws(bar=u'BAR', foo=u'FOO')
u'None FOO BAR'
>>> v2 = JSONRPCTestProxy(siteURL + '/content', jsonId = u'my id',
...     jsonVersion='2.0')
>>> v2.showId()
u'The json id is: my id'
>>> v2.jsonId
u'my id'

混合参数

请注意,关键字参数将存储在request.form中。重要的是要知道,JSON-RPC不支持在一个方法调用中同时使用位置参数和命名参数。

>>> v1.mixedparams('Hello', foo=u'FOO', bar=u'BAR')
Traceback (most recent call last):
...
ValueError: Mixing positional and named parameters in one call is not possible
>>> v11.mixedparams('Hello', foo=u'FOO', bar=u'BAR')
Traceback (most recent call last):
...
ValueError: Mixing positional and named parameters in one call is not possible
>>> v2.mixedparams('Hello', foo=u'FOO', bar=u'BAR')
Traceback (most recent call last):
...
ValueError: Mixing positional and named parameters in one call is not possible

错误处理

看看如果服务器引发异常会发生什么。我们将获得一个包含附加错误内容的响应错误

>>> proxy.forceValueError()
Traceback (most recent call last):
...
ResponseError: Received error from server: ...

错误内容看起来像

>>> proxy.error
{u'message': u'Internal error', u'code': -32603, u'data': {u'i18nMessage': u'Internal error'}}

错误属性将在下一个成功调用时重置

>>> x = proxy.showId()
>>> proxy.error is None
True

现在我们使用一个假的 JSONReader 来强制抛出 ResponseError。但首先我们需要替换我们的 IJSONReader 工具

>>> from z3c.json.interfaces import IJSONReader
>>> sm = site.getSiteManager()
>>> fakeJSONReader = testing.ForceResponseErrorJSONReader()
>>> sm.registerUtility(fakeJSONReader, IJSONReader)

同时设置网站钩子

>>> from zope.component import hooks
>>> hooks.setSite(site)

然后调用一个方法,这会抛出 ResponseError

>>> proxy = JSONRPCTestProxy(siteURL + '/content')
>>> proxy.hello()
Traceback (most recent call last):
...
ResponseError: Unacceptable JSON expression: {"id":"jsonrpc", "method":"hello", "no-params"}

错误信息也存储在代理中

>>> proxy.error
u'Unacceptable JSON expression: {"id":"jsonrpc", "method":"hello", "no-params"}'
传输

在这里我们使用了 JSONRPCTestProxy 进行测试。这是一个 JSON-RPC 代理的包装,它为原始的 JSONRPCProxy 添加了 handleErrors 支持和特殊的传输层,该传输层使用测试调用者。在实际场景中,您可以使用 z3c.json.transport 模块中定义的不同传输层之一与默认的 JSONRPCProxy 实现一起使用。

清理

现在我们需要清理自定义模块。

>>> del sys.modules['custom']

指令

JSONRPC 指令

展示如何使用 jsonrpc 指令。注册指令的元配置。

>>> from zope.configuration import xmlconfig
>>> import z3c.jsonrpc
>>> context = xmlconfig.file('meta.zcml', z3c.jsonrpc)

现在在 z3c:jsonrpc 指令中注册测试模块中定义的视图

>>> context = xmlconfig.string("""
... <configure
...     xmlns:z3c="http://namespaces.zope.org/z3c">
...   <z3c:jsonrpc
...       for="z3c.jsonrpc.testing.IA"
...       class="z3c.jsonrpc.testing.MethodsA"
...       permission="zope.Public"
...       methods="hello"
...       />
... </configure>
... """, context)

让我们检查视图是否注册为适配器

>>> import zope.component
>>> from z3c.jsonrpc.testing import A
>>> from z3c.jsonrpc.testing import TestRequest
>>> a = A()
>>> request = TestRequest()
>>> zope.component.queryMultiAdapter((a, request), name='hello')
<z3c.jsonrpc.zcml.MethodsA object at ...>

我们还可以使用一个层接口,这将限制我们的视图注册到特定的请求类型。提供一个这样的请求类型层

>>> from z3c.jsonrpc.testing import IJSONRPCTestLayer
>>> demoRequest = TestRequest()
>>> zope.interface.directlyProvides(demoRequest, IJSONRPCTestLayer)

并注册一个新的 JSON-RPC 视图

>>> context = xmlconfig.string("""
... <configure
...     xmlns:z3c="http://namespaces.zope.org/z3c">
...   <z3c:jsonrpc
...       for="z3c.jsonrpc.testing.IB"
...       class="z3c.jsonrpc.testing.MethodsB"
...       permission="zope.Public"
...       methods="hello"
...       layer="z3c.jsonrpc.testing.IJSONRPCTestLayer"
...       />
... </configure>
... """, context)

设置新的内容存根

>>> from z3c.jsonrpc.testing import B
>>> b = B()

并在我们的新层中测试视图

>>> zope.component.queryMultiAdapter((b, demoRequest), name='hello')
<z3c.jsonrpc.zcml.MethodsB object at ...>

注意对象 b 不了解默认请求层中的视图

>>> zope.component.queryMultiAdapter((b, request), name='hello') is None
True

setDefaultJSONRPCSkin

>>> from z3c.jsonrpc import interfaces
>>> import z3c.jsonrpc.zcml
>>> class IMySkin(zope.interface.Interface):
...     pass
>>> zope.interface.directlyProvides(IMySkin, interfaces.IJSONRPCSkinType)

在我们设置默认请求之前,我们尝试为我们的请求设置默认请求

>>> from zope.publisher.skinnable import setDefaultSkin
>>> setDefaultSkin(request)

我们的请求不应提供任何默认皮肤,因为我们没有注册任何

>>> IMySkin.providedBy(request)
False

现在让我们注册一个默认皮肤

>>> zope.component.provideUtility(IMySkin, interfaces.IJSONRPCSkinType,
...     name='JSONRPC')
>>> z3c.jsonrpc.zcml.setDefaultJSONRPCSkin('JSONRPC')

我们可以从适配器注册表中查找默认皮肤

>>> from zope.publisher.interfaces import IDefaultSkin
>>> adapters = zope.component.getSiteManager().adapters
>>> default = adapters.lookup((interfaces.IJSONRPCRequest,), IDefaultSkin, '')
>>> default is IMySkin
True

由于我们已将默认皮肤工具注册为我们的请求的皮肤类型,新的请求实例应提供默认皮肤

>>> request = TestRequest()
>>> setDefaultSkin(request)
>>> IMySkin.providedBy(request)
True

我们可以通过查找我们的皮肤类型来获取应用到的默认皮肤

>>> for iface in zope.interface.providedBy(request):
...     if interfaces.IJSONRPCSkinType.providedBy(iface):
...         print "%s" % iface
<InterfaceClass README.IMySkin>

更改

0.7.2 (2013-10-11)

  • handleException:提供可读的跟踪信息

0.7.1 (2012-11-27)

  • 修复 JSONRPCTestTransport 以包含请求完整主机。到目前为止,它消耗了端口。

0.7.0 (2012-03-25)

  • 修复:在 publisher.processInputs 中添加了缺失的异常导入 ParseError

  • 从 python 导入 doctest

0.6.0 (2010-01-27)

  • 清理设置依赖关系,调整 ftesting.zcml

  • 调整覆盖率报告设置

  • 实现了错误视图概念,它将与 ZopePublication 一起工作

  • 实现了已知 zope 和 JSON-RPC 错误的默认错误视图

  • 在响应中使用 DirectResult

  • 删除了未认证的错误视图。这不起作用,并且需要客户端使用的 JavaScript 库支持的定制概念

版本 0.5.4 (2009-04-07)

  • 处理 jsonrpc 请求中的空和不存在参数

版本 0.5.3 (2009-03-10)

  • 修复:在 zope.publisher 中反映皮肤查找更改。使用新的可皮肤化概念。

  • 修复:默认皮肤没有根据从 zope.publisher.browser 实现继承的概念应用,因为我们的 JSON-RPC 请求不提供 IBrowserRequest。添加了一个解决方案,在请求实例创建期间应用给定的 IDefaultSkin。

版本 0.5.2 (2009-02-24)

  • 添加了对所有 JSON-RPC 版本的所有测试

  • 特性:实现了 defaultJSONRPCSkin 指令

  • 特性:支持所有 jsonrpc 版本的非位置参数。现在在处理所有支持的版本的方法参数时没有区别。

  • 修复:对于 jsonrpc 版本 1.1
    • 在成功的情况下不允许提供“error”属性

    • 在错误的情况下不允许提供“result”属性

  • 修复:从 buildout.cfg 中删除了 z3c.json 的开发路径

  • 修复:发布者检查版本 ID 作为字符串而不是浮点数

  • 特性:实现了 JSON-RPC 2.0 规范。使用 JSON-RPC 2.0 版本作为默认版本。可选地,可以设置 1.0 和 1.1 版本。有关更多信息,请参阅 JSON-RPC 2.0 规范。

  • 特性:添加了 JSON-RPC 异常的初始版本。

  • 添加了显式测试清理,因为某些 zope 测试更改留下旧连接的全球适配器注册表

  • 从测试设置中删除了 z3c.layer 的未使用依赖项

  • 删除了 z3c.i18n 的未使用依赖项。

版本 0.5.1 (2008-01-24)

  • 改进元数据。

  • 缺陷:皮肤代码依赖于未发布的API,但实际上后来被回滚了。

版本 0.5.0 (2008-01-21)

  • 初始发布

项目详情


下载文件

下载适合您平台的文件。如果您不确定选择哪个,请了解有关安装包的更多信息。

源分布

z3c.jsonrpc-0.7.2.zip (55.0 kB 查看哈希值)

上传时间

由以下支持

AWSAWS 云计算和安全赞助商 DatadogDatadog 监控 FastlyFastly CDN GoogleGoogle 下载分析 MicrosoftMicrosoft PSF赞助商 PingdomPingdom 监控 SentrySentry 错误日志 StatusPageStatusPage 状态页面