级别: 初级 Mike Olson (), 首席顾问, Fourthought, Inc. Uche Ogbuji (), 首席顾问, Fourthought, Inc.
2002 年 8 月 01 日
XML-RPC 是一种比 SOAP 更早的、简单的轻量级 Web 服务技术。这个月,Mike Olson 和 Uche Ogbuji 将讨论 Python 中的 XML-RPC 工具。
XML-RPC
是 XML Web 服务的鼻祖。它是一个用于远程过程调用(remote procedure call,RPC)的简单规范,这种调用使用
HTTP 作为传输协议,并使用 XML 词汇表作为消息有效负载。由于 XML-RPC
非常简单(整个规范打印出来还不到十页纸),它已经变得非常流行,现在大多数语言都有了标准的或已经可用的 XML-RPC 实现。这些语言中包括
Python,它在版本 2.2 中就开始捆绑 xmlrpclib(Fredrik Lundh 开发的 XML-RPC 实现)了。Joe
Johnston 发表在 IBM developerWorks 上的文章“Using XML-RPC for Web
services”(请参阅 参考资料)
在前三节讨论了 XML-RPC 的基础知识。如果您需要回顾一下这些基本技术,可以从那篇文章开始。在本文中,我们将重点讨论如何使用 Python
实现。要运行本文中的示例,您必须安装 Python 2.2。另外,在上一篇文章中,我们还讨论了 XML-RPC、SOAP
和其他分布式编程技术的性能对比。在作出部署 XML-RPC 的主要决定之前,您可能希望先阅读一下那篇文章。
编写 Python XML-RPC 客户机非常容易。模块 xmlrpclib 拥有所需的全部工具。为调用一个远程 XML-RPC
对象,您可以创建一个代理对象,它用 XML-RPC 把请求转发给服务器。代理对象的外观和感觉就和常规的 Python
对象一样,并且请求就是简单的函数调用。 清单 1(currtime.py)使用 XML-RPC 从 UserLand 服务器获取当前时间(请参阅
参考资料以了解更多关于这个服务的信息)。
清单 1(currtime.py):使用 XML-RPC 获取当前时间
import xmlrpclib #Port 80 is the default server = xmlrpclib.ServerProxy("") currentTimeObj = server.currentTime currtime = currentTimeObj.getCurrentTime() print currtime print currtime.value
|
实际上被代理的是服务器,这个服务器是通过初始化
ServerProxy
类的一个实例建立起来的。我们传入远程服务器的完整 URL(您必须在其中包含 URL 模式“http://”)。端口还是通常的缺省值
80。如果远程服务器不在端口 80 上侦听,而是在端口 8080
上侦听,我们将使用“:8080”。服务器代理把它们托管的所有实际远程对象都当作常规属性,这样我们就可
以得到名为 currentTime 的远程对象的句柄。现在,我们只需调用这个代理对象上的方法即可,它会返回当前时间。响应是一种特殊的 XML-RPC 类型,名为 DateTime。要获得这个对象的纯字符串表示,我们可以使用它的
value 属性。
必须澄清一点:区分服务器内的代理对象这种想法实际上是一种幻想。XML-RPC 允许方法名内包含点号,而且大家一般都习惯使用诸如
pseudo_object.foo 这样的方法名,这个方法名允许客户机把它当作是对名为
pseudo_object 的远程对象的
foo 方法的调用。然而对于 XML-RPC 协议而言,pseudo_object.foo 只是在远程服务器上定义的一个名为
pseudo_object.foo 的方法。稍后您会明白为什么这个区别很重要。
运行该脚本的结果是:
$ python currtime.py
20020808T10:43:06
|
我们已经看到,根据规范定义,
DataType
是 Python 的 XML-RPC 实现中使用的一种特殊类型。如果 XML-RPC 系统要求这种类型的话,就必须使用它来进行 XML-RPC
系统的输入和输出。换句话说,如果一个远程函数接收 DateTime
参数,您就不能向它发送象“20020808T10:43:06”这样的字符串。您将首先构建一个 DateTime 类实例。例如:
datetime_arg = xmlrpclib.DateTime("20020808T10:43:06") remote_obj.method(datetime_arg)
|
还有其他几种这样的特殊类型。
Boolean 和
Binary 是基本数据类型,特殊的
Fault 对象则用于异常处理。自从 Python 2.3 引入了本机(native)布尔类型
bool 后,
Boolean 可能就会逐渐被弃用了。
Binary
与字符串不同,因为它不限制可以传输什么样的字节。其他的 XML-RPC 类型用本机 Python
对象表示,其中列表和元组用来代替数组,字典用来代替结构。需要特别注意的是字符编码。XML-RPC 有一个倍受批评的限制,就是它只支持
ASCII 字符串(或二进制缓冲块)的传输。它根本不提供任何字符编码支持。 清单 2尝试使用 Useful Inc 的样本字符串回送(echo)服务(该服务只是接收一个字符串并返回一个同样的字符串)。
import xmlrpclib server = xmlrpclib.ServerProxy("") eg_obj = server.examples result = eg_obj.stringecho(u"Freude, schönergötterfunken") print result
|
但如果您运行它,就会感受到用户经常抱怨的 Python 中字符编码的苦恼了:
$ python stringecho.py Traceback (most recent call last): File "stringecho.py", line 8, in ? print result UnicodeError: ASCII encoding error: ordinal not in range(128)
|
第一个解决方案是编码为
UTF-8,这个方案看上去不错,但遗憾的是,由于 7 位 ASCII 的范围“容纳”不了
UTF-8,这个解决方案行不通。我们可以使用的解决方案是使用 UTF-7,ASCII 的范围可以容纳它,但这种编码却不常用,并且更冗长。要使用
UTF-7,请用下面的代码代替相应的代码行:
result = eg_obj.stringecho(u"Freude,schönergötterfunken".encode("utf-7"))
|
结果是:
$ python stringecho.py Freude, sch+APY-ne g+APY-tterfunken
|
很遗憾,这个方案也行不
通,因为它会篡改被远程发送的值,并要求允许各方之间进行带外传输,而这种情况下字符串是 UTF-7
编码的。如果可以在各方之间建立这种通信,那么服务器就还可以接受二进制对象而不是字符串,这意味着可以使用
UTF-8。但这只是在逃避协议本身的一个明显的问题,或许这也是考虑使用 SOAP 的一个很好的理由,因为 SOAP 已经完全国际化了。
当服务器需要发出错误信号时,它使用 XML-RPC 的 fault 对象来执行。在 Python 中,这是通过使用一个特殊的异常对象来解释的,该对象会给出一个错误码和一段描述性字符串。
清单 3(getstate.py)使用一个 XML-RPC 服务来根据传进来的数字返回状态名称,该数字代表按字母排序的状态的索引。我们故意使用一个假值
-1,这个值会导致服务器发生错误:
import xmlrpclib server = xmlrpclib.ServerProxy("") eg_obj = server.examples try: result = eg_obj.getStateName(-1) except xmlrpclib.Fault, fault: print fault.faultCode print fault.faultString
|
运行结果是:
$ python getstate.py 800 I don't have a state for the index '-1'
|
如果是 XML-RPC 工具而不是服务本身遇到了问题(例如,您给它提供了一个无法到达的 URL),那么将产生一个
ProtocolError 异常对象而不是
Fault 对象。
Python 还附带
SimpleXMLRPCServer ,它是一个用来实现 XML-RPC 服务器的模块。要公开 XML-RPC 服务,您可以向
SimpleXMLRPCServer
模块中与其同名的类的一个实例注册函数或实例。最直接的方法就是编写带有实现您需求的方法的实例,然后注册这个实例。但在这种情况下方法名不能包含点号,
因此,我们也就不能再使用前面讨论过的手段让服务器看起来代表多个对象。这样一来的影响不久将变得非常清楚,但首先让我们来创建一个日历服务器,比如我们
一直用来演示 SOAP 服务器的那种。 清单 4(calserver.py)是 XML-RPC 日历服务器实现:
import calendar, SimpleXMLRPCServer #The server object class Calendar: def getMonth(self, year, month): return calendar.month(year, month) def getYear(self, year): return calendar.calendar(year) calendar_object = Calendar() server = SimpleXMLRPCServer.SimpleXMLRPCServer(("localhost", 8888)) server.register_instance(calendar_object) #Go into the main listener loop print "Listening on port 8888" server.serve_forever()
|
Calendar 类实现了我们想公开的方法。这些方法接收数字并返回字符串。我们先创建这个对象的一个实例,然后再创建 XML-RPC 服务器类的一个实例,我们向后一个实例注册日历实例。这立刻会使
getMonth 和
getYear
方法在服务器上可用。请记住,Python 中的类型是动态定义的,所以多数情况下您会希望向方法添加类型检查代码。当然,Python
丰富的表达方式使得我们可以轻而易举地做到这一点,同时还意味着您能够很容易地进行类型检查,而且这些类型检查可以比多数语言所允许的更复杂。在主代码
中,我们创建一个服务器对象,为其提供一个代表侦听地址和端口的元组。这个地址可以是一个主机名或 IP
地址。最后,我们把这个服务器放入它的主循环中,这个循环只在操作系统发信号中断它(比如用户按下
CTRL-C)时才中断。请打开另外一个控制台并运行服务器脚本。
为测试该服务器,我们在
清单 5(calclient.py)中编写了一个简单的客户机:
import xmlrpclib server = xmlrpclib.ServerProxy("") month = server.getMonth(2002, 8) print month
|
在这里,您可以看到我们不在方法名中包含点号的影响。我们不是首先从服务器获得一个伪对象(它实际上只表示从方法名的点号前提取的那一部分),而是只需调用服务器代理本身的方法。输出结果是:
$ python calclient.py August 2002 Mo Tu We Th Fr Sa Su 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
XML-RPC 比 SOAP 更简单,在开放源代码的项目中非常流行。它是任何语言或框架目前都必须提供的基本工具箱,所以在 Python
中添加新的 XML-RPC
模块是很受欢迎的。但它也有自己的缺点,主要就是对字符编码的支持不够,它的字符编码支持对英语有一种令人吃惊的偏向,在国际化的重要性已被充分理解的今
天看来,这是很不合时宜的。
在下一部分,我们将研究针对另一个已经广泛流行的 Web 服务接口的 Python 工具,这个接口就是 RDF 站点摘要(RDF site summary,RSS)。
- 您可以参阅本文在 developerWorks 全球站点上的
英文原文.
- 请单击文章顶部或底部的
讨论参与本文的
。
- 从
开始探究 XML-RPC。
也非常容易阅读。
- 关于参考资料,请使用 XML-RPC
客户机和
服务器模块的官方 Python 文档。
- UserLand 提供
XML-RPC interface for Current Time。
- 请浏览
以获得更多的
。
- 如果您打算用协议做一些非常重要的工作,请阅读 Eric Kidd 的
并将其加入您的书签。
IBM 参考资料
| | | Mike Olson 是
Fourthought Inc.的一名顾问,也是该公司的创始人之一,Fourthought Inc 是一家软件供应商,专门从事企业知识管理应用程序的 XML 解决方案方面的咨询工作。Fourthought 还开发了
4Suite,一个面向 XML 中间件的开放源代码平台。您可以通过
与 Olson 先生联系。
|
| | | Uche Ogbuji 是
Fourthought Inc.的一名顾问,也是该公司的创始人之一,Fourthought Inc. 是一家软件供应商,专门从事企业知识管理应用程序的 XML 解决方案方面的咨询工作。Fourthought 还开发了
4Suite,一个面向 XML 中间件的开放源代码平台。Ogbuji 先生是一位出生于尼日利亚的计算机工程师和作家,在美国科罗拉多州的 Boulder 工作、居住。您可以通过
与 Ogbuji 先生联系。
|
|