个人工具

Zope3宝典/使用XML-RPC访问

来自Ubuntu中文

跳转至: 导航, 搜索

Chapter 23 Availability via XML-RPC(第 23 章:使用XML-RPC访问)


原文出处:The Zope 3 Developers Book - An Introduction for Python Programmers

原文作者:StephanRichter, zope.org

授权许可:创作共用协议

翻译人员:

校对人员:Leal

适用版本:Zope 3

文章状态:校正阶段



Difficulty(难度)

Sprinter (进阶者)

Skills(技能)

  • Be familiar with the Message Board demo package up to this point.
    熟悉留言薄演示包
  • Feel comfortable with writing ZCML-based configuration.
    能轻松编写基于 ZCML 的配置
  • Some insight in the publisher framework. Optional.
    对发布框架有一些深入了解。可选


Problem/Task(问题/任务)

A very common way to communicate with remote systems is via XML-RPC, a very light-weight protocol on top of HTTP. Zope’s HTTP server comes by default with a XML-RPC component. If we want to allow other systems to communicate with our message board, then we need to declare the methods that will be available via XML-RPC.
一个用来同远程系统通讯的非常通用的方式就是通过 XML-RPC ,它是一个基于 HTTP 的非常轻量级的协议。Zope 的 HTTP 服务缺省支持 XML-RPC 组件。如果我们要其他系统同我们的留言薄通讯的话,那么我们就需要通过 XML-RPC 来声明方法。

Solution(解决方案)

You might wonder at this point why we don’t simply map all the existing methods to XML-RPC and be done with it. There are three reasons for not doing this. First, XML-RPC handles only a limited amount of data types. In the following table you see a mapping of Python types to XML-RPC data type elements:
这时你也许想知道我们为什么不简单地将所有存在的方法都映射到 XML-RPC 上并通过它来完成呢。有三个方面的原因不能这么做。首先,XML-RPC 只处理几个有限的数据类型。在下表中你可以看到 Python 类型映射到 XML-RPC 的数据类型元素:

  • integer <- - - -> <int>
  • float <- - - -> <double>
  • string <- - - -> <string>
  • list <- - - -> <array>
  • dict/struct <- - - -> <dict>
  • bool <- - - -> <boolean>
  • xmlrpclib.Binary <- - - -> <binary>
  • xmlrpclib.DateTime <- - - -> <dateTime>

As you can see, there is no support for None and unicode, which are huge drawbacks for XML-RPC in general.
正如你所见,None 和 unicode 类型并不被支持,一般来说这也是 XML-RPC 最大的缺陷。

Second, another disadvantage is the lack of keyword arguments. XML-RPC only understands regular positional arguments and arguments with default values. Third, since Python returns None by default for methods, all methods that do not have a return value are doomed.
其次,另一个缺陷在于没有关键词变量。 XML-RPC 只能理解规定位置的变量和缺省值的变量。第三,因为 Python 方法缺省返回 None,所有没有返回值的方法都会被掉弃。

Now that we have briefly discussed the shortcomings of XML-RPC, we should look ahead and say that XML-RPC is still a very powerful tool that can be used without much hassle.
现在我们大致地讨论了一下 XML-RPC 的缺点,我们应该向前看,应该说 XML-RPC 无疑是能被使用的非常强大的工具。

23.1 Step I: Creating “Methods” XML-RPC Presentation Components(23.1 步骤 I: 创建 XML-RPC 表示组件方法)

Obviously we have to create a view component for both content objects, MessageBoard and Message. However, both share the functionality that they can contain and manage sub-messages, so that it is desired to factor out this functionality into a common base class, MessageContainerMethods. Since we want to keep the XML-RPC code separate, create a new module called xmlrpc.py in the messageboard directory and add the following content:
显然我们必须为内容对象 MessageBoard 和 Message 创建视图组件。然而,由于两者共有的功能是它们可以包含和管理子消息,因此,要将该功能分离出来放在一个公共的基类 MessageContainerMethods 中。因为我们想保持 XML-RPC 代码的独立性,因此在留言薄文件夹中新建一个名为 xmlrpc.py 的模块,并添加下列内容:

#!python
from zope.event import notify
from zope.app.publisher.xmlrpc import MethodPublisher

from zope.app.event.objectevent import ObjectCreatedEvent

from book.messageboard.message import Message

class MessageContainerMethods(MethodPublisher):

def getMessageNames(self):
"""Get a list of all messages."""
return list(self.context.keys())

def addMessage(self, name, title, body):
"""Add a message."""
msg = Message()
msg.title = title
msg.body = body
notify(ObjectCreatedEvent(msg))
self.context[name] = msg
return name

def deleteMessage(self, name):
"""Delete a message. Return True, if successful."""
self.context.<u>delitem</u>(name)
return True
  • Line 8: Make the class a MethodPublisher, which is similar to a BrowserView and its constructor will expect a context object and a XMLRPCRequest instance.
    第 8 行: 生成一个 MethodPublisher 类,它与 BrowserView 相似且它的构造函数需要一个上下文对象和一个 XMLRPCRequest 实例。
  • Line 10-12: Return a list of all message names. But remember, we implemented the containment with BTrees (see first chapter of this part), so that keys() will not return a list or tuple as a result, but a btree-items object. Therefore we need to implicitly cast the result to become a list, so that XML-RPC is able understand it.
    第 10-12 行: 返回所有消息名列表。但请记住,我们实现与 BTrees 的包容(参见本部分的第一章),因此 key() 不能返回列表或元组作为结果,除了 btree-item 对象。因此我们需要将结果变成一个列表,以便 XML-RPC 能够明白它。
  • Line 19: Since we want to play nice with the system, let it know that we created a new content object.
    第 19 行: 因为我们想让系统运行良好,想让它知道我们已经新建了一个内容对象。
  • Line 21, 26: Make sure we return something, so that XML-RPC will not be upset with us. As mentioned before, if no return value is specifed, None is implicitly returned, which XML-RPC does nto understand.
    第 21, 26 行: 保确我们能返回数据,以便 XML-RPC 不会混乱。如前面所讲,如果没有指定返回值则 None 会被缺省返回,而 XML-RPC 是不能明白的。

The two actual views for our content objects are now also implementing accessor and mutator methods for their properties. So here are the two views:
内容对象的两个真实视图现在也实现了 accessor 和 mutator 方法

#!python
from zope.app.event.objectevent import ObjectModifiedEvent

class MessageMethods(MessageContainerMethods):

def getTitle(self):
return self.context.title

def setTitle(self, title):
self.context.title = title
notify(ObjectModifiedEvent(self.context))
return True

def getBody(self):
return self.context.body

def setBody(self, body):
self.context.body = body
notify(ObjectModifiedEvent(self.context))
return True


class MessageBoardMethods(MessageContainerMethods):

def getDescription(self):
return self.context.description

def setDescription(self, description):
self.context.description = description
notify(ObjectModifiedEvent(self.context))
return True
  • Line 10, 18 & 29: When modifying a message board or message, we have to explicitly send out a modification notification event. We did not have to deal with this until now, since for browser forms these events are created automatically by the forms machinery.
    第 10, 18 & 29 行: 当修改留言薄或消息时,我们必须明确发出修改通知事件。直至目前我们也没必要处理这些,因为这些事件被浏览器表单的事件机制自动创建了。
  • Line 11, 19 & 30: Again, we need to make sure we do not just return None from a method.
    第 11, 19 & 30 行: 再一次,我们需要确保我们不能从方法中返回 None。

That’s already everything from a coding point of perspective. But before we hook up the code in the component architecture, we need to do some testing.
从编程的角度一切准备就绪。但在我们将代码放入组件架构之前,我们还需要做一些测试。

23.2 Step II: Testing(23.2 步骤 II: 测试)

Of course, the testing code is multiples more complex than the actual implementation, since we have to bring up the component architecture and the event service manually. Similar to the implementation, we can again separate the container-related tests in a base class (the code should be located in tests/test_xmlrpc.py):
当然,测试代码相对实际实现来说是成倍复杂的,因为我们不得不手工提出组件架构和事件服务。与实现相同,我们可以在基类中分离出容器相关的测试(代码被放置在 tests/test_xmlrpc.py 中):

#!python
from zope.app import zapi
from zope.app.tests.placelesssetup import PlacelessSetup

class MessageContainerTest(PlacelessSetup):

def _makeMethodObject(self):
return NotImplemented

def _makeTree(self):
methods = self._makeMethodObject()
msg1 = Message()
msg1.title = 'Message 1'
msg1.description = 'This is Message 1.'
msg2 = Message()
msg2.title = 'Message 1'
msg2.description = 'This is Message 1.'
methods.context['msg1'] = msg1
methods.context['msg2'] = msg2
return methods

def test_getMessageNames(self):
methods = self._makeTree()
self.assert_(isinstance(methods.getMessageNames(), list))
self.assertEqual(list(methods.context.keys()),
methods.getMessageNames())

def test_addMessage(self):
methods = self._makeTree()
self.assertEqual(methods.addMessage('msg3', 'M3', 'MB3'),
'msg3')
self.assertEqual(methods.context['msg3'].title, 'M3')
self.assertEqual(methods.context['msg3'].body, 'MB3')

def test_deleteMessage(self):
methods = self._makeTree()
self.assertEqual(methods.deleteMessage('msg2'), True)
self.assertEqual(list(methods.context.keys()), ['msg1'])
  • Line 6-7: The implementation of this method should return a valid XML-RPC method publisher.
    第 6-7 行: 该方法的实现将返回一个合法的 XML-RPC 方法发布
  • Line 9-19: Create an interesting message tree, so that we have something to test with.
    第 9-19 行: 创建一个兴趣消息树,以便我们可以用它来进行测试。
  • Line 21-25: Make sure the names list is converted to a Python list and all elements are contained in it.
    第 21-25 行: 确保名称列表被转换成 Python 列表且所有元素都被包含在其中。
  • Line 27-32: This method obviously tests the adding capability. We just try to make sure that the correct attributes are assigned to the message.
    第 27-32 行: 显然该方法测试新加的功能。我们试图确保正确的属性被指定到消息。
  • Line 34-37: Simply checks that a message is really deleted.
    第 34-37 行: 简单检查消息确实被删除了。

Now that we have the base class, we can implement the real test cases and add tests for the property accessors and mutators:
现在我们有了基类,我们可以实现真实测试代码并为 accessors 和 mutators 的属性添加测试。

#!python
import unittest

from zope.publisher.xmlrpc import TestRequest

from book.messageboard.message import Message
from book.messageboard.messageboard import MessageBoard
from book.messageboard.xmlrpc import MessageBoardMethods, MessageMethods

class MessageBoardMethodsTest(MessageContainerTest, unittest.TestCase):

def _makeMethodObject(self):
return MessageBoardMethods(MessageBoard(), TestRequest())

def test_description(self):
methods = self._makeTree()
self.assertEqual(methods.getDescription(), '')
self.assertEqual(methods.setDescription('Board 1') , True)
self.assertEqual(methods.getDescription(), 'Board 1')

class MessageMethodsTest(MessageContainerTest, unittest.TestCase):

def _makeMethodObject(self):
return MessageMethods(Message(), TestRequest())

def test_title(self):
methods = self._makeTree()
self.assertEqual(methods.getTitle(), '')
self.assertEqual(methods.setTitle('Message 1') , True)
self.assertEqual(methods.getTitle(), 'Message 1')

def test_body(self):
methods = self._makeTree()
self.assertEqual(methods.getBody(), '')
self.assertEqual(methods.setBody('Body 1') , True)
self.assertEqual(methods.getBody(), 'Body 1')


def test_suite():
return unittest.TestSuite((
unittest.makeSuite(MessageBoardMethodsTest),
unittest.makeSuite(MessageMethodsTest),
))

if <u>name</u> == '<u>main</u>':
unittest.main(defaultTest='test_suite')
  • Line 11-12 & 22-23: Create a XML-RPC method publisher for the message board and the message, respectively. To do that we need an object instance (no problem) and an XML-RPC request. Luckily, like for the browser publisher, the XML-RPC publisher provides a TestRequest which was written for its easy usage in unit tests like these.
    第 11-12 & 22-23 行: 为留言薄和消息各创建一个 XML-RPC 方法发布,要做到这点我们需要一个对象实例(没问题)和一个 XML-RPC 请求。幸运的是,与浏览器发布相似,XML-RPC发布也提供了一个为在单元测试中能方便使用的 TestRequest。
  • Line 38-45: And again the usual unit test boiler plate.
    And again the usual unit test boiler plate.

The rest of the code is not so interesting and should be obvious to the reader. Please run these tests now and make sure that everything passes.
代码的其他部分显然不那么会引起读者的兴趣。现在请运行这些测试并确保通过。

23.3 Step III: Configuring the new Views(23.3 步骤 III: 配置新的视图)

To register the XML-RPC views, you need to import the xmlrpc namespace into your main configuration file using
为了注册 XML-RPC 视图,你需要将 xmlrpc 名称空间导入到你正在使用的主要配置文件

1  xmlns:xmlrpc="http://namespaces.zope.org/xmlrpc"

in the zopeConfigure element. Now you simply add the following two directives to the configuration:
在 zopeConfigure 元素中。现在你可以简单地将下列两个语句添加到配置中:

1  <xmlrpc:view
2      for=".interfaces.IMessageBoard"
3      permission="book.messageboard.Edit"
4      methods="getMessageNames addMessage deleteMessage
5               getDescription setDescription"
6      class=".xmlrpc.MessageBoardMethods" />
7  
8  <xmlrpc:view
9      for=".interfaces.IMessage"
10      permission="book.messageboard.Edit"
11      methods="getMessageNames addMessage deleteMessage
12               getTitle setTitle getBody setBody"
13      class=".xmlrpc.MessageMethods" />
  • Line 2: This view is for IMessageBoard objects.
    第 2 行: 该视图是针对 IMessageBoard 对象的
  • Line 3: XML-RPC views require the book.messageboard.Edit permission, which means that someone has to authenticate before using these methods.
    第 3 行: XML-RPC 视图要求 book.messageboard.Edit 权限,这就意味着人们在使用这些方法之间需要授权。
  • Line 4-5: This is the list of methods that will be available as XML-RPC methods on the messageboard.
    第 4-5 行: 这是个方法列表,它们在 messageboard 中作为 XML-RPC 方法是可用的。
  • Line 6: The method publisher class is .xmlrpc.MessageBoardMethods, which provides the previously defined methods.
    第 6 行: 方法发布类是 .xmlrpc.MessageBoardMethods,它提供先前定义的方法
  • Line 8-13: Repeat the previous precedure for IMessage components.
    第 8-13 行: 为 IMessage 组件重复前面的步骤

Now you can restart Zope 3 and give your XML-RPC methods a run. But oh no, how do we test this? Certainly a browser will not get us very far.
现在你可以重启 Zope 3 了并运行一下你的 XML-RPC 方法。但是哦不,我们怎么来测试呢?浏览器肯定不会给我们太多帮助。

23.4 Step IV: Testing the Features in Action(23.4 步骤 IV: 在运行中测试特性)

Python has a really nice XML-RPC module that can be used as XML-RPC client and also provides some extra help with basic authentication. In order to save the reader some typing, I have provided a module called xmlrpc_client.py in the messageboard package which you can call from the command line:
Python 有一个真正好用的 XML-RPC 模块,它可以被为 XML-RPC 客户端来使用,也可以提供一些有着基本授权的额外帮助。为了能保存读者的一些输入,我在留言薄包中提供了一个名为 xmlrpc_client.py 的模块,你可以从命令行中调用:

1  # ./xmlrpc_client.py

You will be asked for the URL to your messageboard object, your username and password. Once this is done, you are presented with a normal Python prompt, except that there is a local variable called board available, which represents your XML-RPC connection. You can now use the available methods to manipulate the board at your heart’s content.
你将被询问到你留言薄对象的 URL,你的用户名和密码。当这些完成后,你将得到一个正常的 Python 提示符,只是有个叫 board 的本地变量可用,用来表示你的 XML-RPC 连接。你现在可以使用可用的方法在你的中心内容中操作 board 。

1  # ./messageboard/xmlrpc_client.py
2  Message Board URL [http://localhost:8080/board/]:
3  Username: srichter
4  Password: ........
5  The message board is available as 'board'
6  Python 2.3 (#2, Aug 31 2003, 17:27:29)
7  [GCC 3.3.1 (Mandrake Linux 9.2 3.3.1-1mdk)] on linux2
8  Type "help", "copyright", "credits" or "license" for more information.
9  (InteractiveConsole)
10  >>> board.getDescription()
11  'First Message Board'
12  >>> board.getMessageNames()
13  ['msg1', 'msg2']
14  >>> board.msg1.getMessageNames()
15  ['msg11']
16  >>> board.msg1.msg11.getTitle()
17  'Message 1-1'
18  >>> board.msg1.msg11.getBody()
19  'This is the first response to Message 1.'
20  >>> board.msg1.addMessage('msg12', 'Message 1-2', 'Second response!')
21  'msg12'
22  >>> board.msg1.getMessageNames()
23  ['msg11', 'msg12']
24  >>> board.msg1.msg12.getTitle()
25  'Message 1-2'
26  >>> board.msg1.deleteMessage('msg12')
27  True
28  >>> board.msg1.getMessageNames()
29  ['msg11']
30  >>>

Figure 23.1: A sample XML-RPC session using the provided client.
图 23.1: 一个使用客户端的 XML-RPC 会话示例

Exercises(练习)

  • We only made a very limited amount of the message board’s functionality available via XML-RPC. Write a new XML-RPC method publisher to
    我们通过 XML-RPC 实现了非常有限的留言薄功能。编写一个新的 XML-RPC 方法发布以实现:

a. manage mail subscriptions for messages.
管理消息的邮件描述

    • manage the workflows of messages.
      管理消息的工作流
  • Due to the simple implementation of this chapter, message attachments are treated like messages when displaying message names. Improve the implementation and the API to handle attachments properly. This includes writing a method publisher for IFile components.
    由于本章的简单实现,当显示消息名时对待消息附件就象消息一样。改进实现和 API 以便更适当的处理附件。这包括为 IFile 组件写一个方法发布。