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的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 6b34a82b27eb0c964c9c106a7433b6d3a2dd75c7516daf17f5992bf2faae22f3 |
|
MD5 | 877a9417c7ed7b8ceff1ff6cbd196dd7 |
|
BLAKE2b-256 | f874aa167a2a60c2c4e32e408fee626f5fae819e3ca69dd79b7e2093834ac0f6 |