在这个由两部分组成的系列的第一篇文章中,Web 服务专栏作家 Mike Olson 和 Uche Ogbuji 讨论了 Python 可以使用的各种 SOAP 实现,并给出了详细的代码示例。
在前面的 3 部分中,我们已经用 4Suite Server 开发了一个 Web 服务实现,并利用了该产品的 SOAP 支持。(请参阅 参考资料。)Python 还有其它的 SOAP 实现;实际上,这好象成了很流行的使用 Python 的开放源代码活动。在本文中,我们将看一下工作中的 Soap.py。关于其它开放源代码的 SOAP 项目的更新,请参阅 旁注。但我们马上要讨论的麻烦问题,是 Python SOAP 模块的命名。不同项目之间的交流看上去好象并不多,因为在这些项目的名称间有许多令人困惑的类似的地方。最近,在向同事解释这些供选方案时,我们发现自己记不清楚 SOAPy 和 SOAP.py 的特征分别是什么了 ― 而且这还是在我们用过它们很长时间后。这个问题在必须为实际库中的多个模块命名时变得更为严重,请参阅旁注。
在这个专栏的前三部分中,我们讨论了 4Suite SOAP;在这篇和下篇文章中,我们将提供来自 SOAP.py 和 SOAPy 项目的示例,在这两个项目冻结时,这好象是最深入的讨论了。注意,尽管 W3C 的 XML 协议工作组已经制订了一个被称为 SOAP 1.2 的草案,但各个平台和语言上 SOAP 实现的普遍级别仍是 SOAP 1.1,更早的版本甚至更具代表性。这些日子 SOAP 版本的发展引入了一些复杂性,这些复杂性可能会超过 SOAP 所承诺的简洁性。
SOAP.py 包含的是一些基本的东西。没有 Web 服务描述语言(Web Services Description Language,WSDL)或者任何其它附加的东西,只有用 Python 实现的 SOAP 客户机和服务器的透明支持。甚至这个包中的一个很好的功能也只是与基础架构相关:SOAP.py 支持安全套接字层(SSL)用于加密的 SOAP 传输。为使用这个功能,您必须安装 M2Crypto,M2Crypto 是一个库,包含各种加密工具和格式,从 RSA 和 DSA 到 HTTPs、S/MIME 等等。在这一部分,我们不准备讨论 SOAP.py 的 SSL 支持。
|
开始先下载分发包(在写这篇文章的时候,SOAPpy 0.9.7 是最新的分发包),把文件解包,转到结果目录,并把文件 SOAP.py复制到自己倾向的位置。当然,这个“倾向”就是需要技巧的地方。由于这些 SOAP lib 中有很多都使用大小写组合不同的“soap.py”作为模块名,所以大家一定要小心。当然,UNIX 用户只需关心大小写是否精确匹配,但对于 Windows 用户来说,甚至“SOAP.py”和“soap.py”之间的冲突也会带来麻烦。Orchard 的 SOAP.py 也有一个容易发生冲突的名称,但它有可能避开所有的问题,因为它的模块聪明地放在了 Orchard 包中。
上面的内容简言之就是建议您确保安装所有的 Python SOAP 模块时都使用与众不同的包名称。在我们的案例中,我们在 PYTHONPATH 中发现了一个合适的目录并创建了一个 WebServices 包,把 SOAP.py 放在了这个包中。因此,在 Linux 中:
$ mkdir ~/lib/python/WebServices $ touch ~/lib/python/WebServices/__init__.py $ cp SOAPpy097/SOAP.py ~/lib/python/WebServices |
请注意很重要的第二条命令,它将生成一个 __init__.py
文件,这个文件将 WebServices 目录标志为 Python 包。如果您需要把这些代码打包成 Windows 版本,您可能希望向空文件中输入一些注释,因为一些 Windows 工具不创建空文件。
对于公开提供的 SOAP 服务器,早已经有了好几个活动的注册中心。最流行的可能是 XMethods。当然,它也是一个相当有趣的指导,通过它我们可以了解 SOAP 的实际状况,而不要听它的吹嘘。这里的大多数公共 Web 服务仍然只是一些无关紧要的东西,几乎不值得我们勇敢的新模型多费口舌,但那是另一回事了。实际上,我们将选择一个公共服务来演示和测试如何把 SOAP.py 作为 SOAP 客户机使用。
或者,我们可以试试。作者尝试的第一个服务,卫生保健提供者定位器,在遇到下列报错消息时显示 SOAP 互操作性的当前状态中的陷阱:
WebServices.SOAP.faultType: <Fault soap:Client: Server did not recognize the value of HTTP Header SOAPAction: "".> |
哦。SOAPAction 是一个 HTTP 头,应该是用来标记被访问服务的。它是 SOAP 请求中必需的头,但即便是设置了所需的头(只是一对空的双引号)后,上面的错误仍然存在。作者发现大多数 MS SOAP 实现都存在这个问题。在试遍了这些服务后,我们断定,Delphi 实现好象与 SOAP.py 合作得最好,但在试服务时 — 即使是用 Delphi 实现时,也返回复杂的类型,比如列表,SOAP.py 无法使用它们,返回不带数据的 WebServices.SOAP.typedArrayType
实例。
最后,作者选择了一个相当合适的 Web 服务,该服务返回漫画《丁丁历险记》中的人物 Haddock 船长常用的骂人语言(是的,大多数 Web 服务都是这样)。 清单 1(curse.py)就是这个程序。
清单 1:访问 Curse 生成器 SOAP 服务的 SOAP.py 程序
#!/usr/bin/env python #http://xmethods.net/detail.html?id=175 import sys #Import the SOAP.py machinery from WebServices import SOAP remote = SOAP.SOAPProxy( "http://www.tankebolaget.se/scripts/Haddock.exe/soap/IHaddock", namespace="urn:HaddockIntf-IHaddock", soapaction="urn:HaddockIntf-IHaddock#Curse" ) try: lang = sys.argv[1] except IndexError: lang = "us" result = remote.Curse(LangCode=lang) print "What captain Haddock had to say: "%s""%result |
|
导入库后,我们将设置代理对象 remote
。这个对象将方法调用转换为远程 SOAP 消息。它的初始化器使用管理远程请求的关键参数:服务器的 URI(被称为“端点”)、请求元素的 XML 名称空间(通过它,SOAP-as-RPC 将口头承诺变成 XML 基础)和 SOAPAction 头值。
接下来,我们将确定方法参数,对于这个 Web 服务来说,方法参数只是 Haddock 骂人的语言,瑞典语(“se”)或英语(奇怪的是,是“us”而不是“en”)。
最后,我们调用名称正确的方法,代理对象的 Curse
进行 SOAP 调用,然后打印出结果。下面的会话演示了对该程序的使用:
$ python curse.py What captain Haddock had to say: "Ectoplasmic Byproduct!" |
用 SOAP.py 实现 SOAP 服务器相当容易。作为一个示例,我们将仿建字段,还要实现一个很常见的服务:一个程序,给出年份和月份,它将以字符串的形式打印出日历。它的程序服务器是 清单 2(calendar-ws.py)。
#!/usr/bin/env python import sys, calendar #Import the SOAP.py machinery from WebServices import SOAP CAL_NS = "http://uche.ogbuji.net/eg/ws/simple-cal" class Calendar: def getMonth(self, year, month): return calendar.month(year, month) def getYear(self, year): return calendar.calendar(year) server = SOAP.SOAPServer(("localhost", 8888)) cal = Calendar() server.registerObject(cal, CAL_NS) print "Starting server..." server.serve_forever() |
进行过必要的导入后,我们为自己的服务器定义 SOAP 请求元素期望的名称空间( CAL_NS
)。接下来我们定义实现所有方法的类,这些方法将被公开为 SOAP 方法。大家也可以把单个函数作为 SOAP 方法注册,但使用类方法是最灵活的,特别是当您想管理调用间的状态时。这个 Calendar
类定义了一个方法 getMonth
,该方法使用 Python 的内置日历模块在文本表单中返回月度日历,同时它还定义了另一个返回整年日历的方法。
然后创建 SOAP 服务器框架的一个实例,这个实例还带有侦听端口 8888 的指令。我们还必须创建 Calendar
类的一个实例,这个实例在下一行中被注册用来处理 SOAP 消息,同时为其指出相关的名称空间。最后,我们调用 serve_forever
方法,该方法直到进程终止才返回。
为运行服务器,请打开另一个命令 shell 并执行 python calendar-ws.py
。执行结束时使用 ctrl-C 杀死进程。
我们本来可以用也是用 SOAP.py 写的客户机测试服务器,但那太显而易见了。我们还是用低级 Python 编写客户机把 SOAP 响应作为 XML 字符串来构建,并发送一条 HTTP 消息。这个程序(testcal.py)在 清单 3中。
import sys, httplib SERVER_ADDR = "127.0.0.1" SERVER_PORT = 8888 CAL_NS = "http://uche.ogbuji.net/ws/eg/simple-cal" BODY_TEMPLATE = """<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:s="http://uche.ogbuji.net/eg/ws/simple-cal" xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance" xmlns:xsd="http://www.w3.org/1999/XMLSchema" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" > <SOAP-ENV:Body> <s:getMonth> <year xsi:type="xsd:integer">%s</year> <month xsi:type="xsd:integer">%s</month> </s:getMonth> </SOAP-ENV:Body> </SOAP-ENV:Envelope>""" def GetMonth(): year = 2001 month = 12 body = BODY_TEMPLATE%(year, month) blen = len(body) requestor = httplib.HTTP(SERVER_ADDR, SERVER_PORT) requestor.putrequest("POST", "cal-server") requestor.putheader("Host", SERVER_ADDR) requestor.putheader("Content-Type", "text/plain; charset="utf-8"") requestor.putheader("Content-Length", str(blen)) requestor.putheader("SOAPAction", "http://uche.ogbuji.net/eg/ws/simple-car") requestor.endheaders() requestor.send(body) (status_code, message, reply_headers) = requestor.getreply() reply_body = requestor.getfile().read() print "status code:", status_code print "status message:", message print "HTTP reply body:/n", reply_body if __name__ == "__main__": GetMonth() |
下面的会话演示了这个测试的运行情况。
$ python testcal.py status code: 200 status message: OK HTTP reply body: <?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/1999/XMLSchema" xmlns:SOAP- ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance" xmlns:SO AP-ENC="http://schemas.xmlsoap.org/soap/encoding/"> <SOAP-ENV:Body> <getMonthResponse SOAP-ENC:root="1"> <Result xsi:type="xsd:string"> December 2001 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 </Result> </getMonthResponse> </SOAP-ENV:Body> </SOAP-ENV:Envelope> |
如果您查找行 self.debug = 0
并把“0”改为“1”(这是 SOAP.py 版本 0.9.7 中的第 210 行),有一件要注意的事情是您可以获得被交换的实际 SOAP 消息的详细信息和用于调试与跟踪的其它关键数据,这对您很有用。作为示例,下面提供了一个会话,它是打开了调试信息显示开关的以前的 curses.py 程序的一个会话:
$ python curse.py *** Outgoing HTTP headers ********************************************** POST /scripts/Haddock.exe/soap/IHaddock HTTP/1.0 Host: www.tankebolaget.se User-agent: SOAP.py 0.9.7 (actzero.com) Content-type: text/xml; charset="UTF-8" Content-length: 523 SOAPAction: "urn:HaddockIntf-IHaddock#Curse" ************************************************************************ *** Outgoing SOAP ****************************************************** <?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/1999/XMLSchema" xmlns:SOAP- ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance" xmlns:SO AP-ENC="http://schemas.xmlsoap.org/soap/encoding/"> <SOAP-ENV:Body> <ns1:Curse xmlns:ns1="urn:HaddockIntf-IHaddock" SOAP-ENC:root="1"> <LangCode xsi:type="xsd:string">us</LangCode> </ns1:Curse> </SOAP-ENV:Body> </SOAP-ENV:Envelope> ************************************************************************ *** Incoming HTTP headers ********************************************** HTTP/1.? 200 OK Server: Microsoft-IIS/5.0 Date: Tue, 11 Sep 2001 16:40:19 GMT Content-Type: text/xml Content-Length: 528 Content: ************************************************************************ *** Incoming SOAP ****************************************************** <?xml version="1.0" encoding="UTF-8"?><SOAP-ENV:Envelope xmlns:SOAP- ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/1999/XMLSchema" xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xml soap.org/soap/encoding/"><SOAP-ENV:Body><NS1:CurseResponse xmlns:NS1="urn:HaddockIntf- IHaddock" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><NS1:return xsi:type="xsd:string">Anacoluthons!</NS1:return></NS1:CurseRespon se></SOAP-ENV:Body></SOAP-ENV:Envelope> ************************************************************************ What captain Haddock had to say: "Anacoluthons!" |
为进行比较,您可以在带有下列代码的旧的 Python 脚本或程序中获得相同的信息:
import calendar return calendar.month(2001, 10) |
|
我们已经注意到了,虽然 SOAP.py 的互操作性还存在一些问题,但可用的调试工具可望提供帮助 ― 这个专栏的一位作者 Mike Olson 是已经签约要帮助这个项目继续发展的开发者之一。我们将看一下另一个 Python SOAP 实现。
- 您可以参阅本文在 developerWorks 全球站点上的 英文原文.
- 请单击文章顶部或底部的 讨论参与本文的 讨论论坛。
- XMethods:一个 SOAP 服务注册中心。
- The Daily Python-URL,由 Secret Labs AB 的 Fredrik Lundh 编撰。
- M2Crypto:Python 的一个加密库。
Python 的 SOAP 实现
- SOAPy:Python 的一个 SOAP/XML 模式库。另请参阅 Source Forge SOAPy 项目页面。
- SOAP.py,Python 项目的一个 Web 服务项目。另请参阅 开发冻结通知。
- PySOAP,旨在作为 SOAP v1.1 标准的 Python 实现。
- soaplib由 Secret Labs 管理。
- 请参阅 Orchard Sourceforge 主页。
IBM 参考资料
- IBM's Web services fact sheet简要说明了 SOAP 如何适应 Web 服务倡导,并带有到其它参考资料的链接。
- 查阅 Python Web 服务开发者的前两个专栏:
Mike Olson 是 Fourthought Inc.的一名顾问,也是该公司的创始人之一,这个公司是一个软件供应商,专门从事企业知识管理应用程序的 XML 解决方案方面的咨询工作。Fourthought 开发了 XML 中间件的开放源代码平台 4Suite和 4Suite Server。您可以通过 mike.olson@fourthought.com与 Mr. Olson 联系。 |
Uche Ogbuji 是 Fourthought Inc.的一名顾问,也是该公司的创始人之一,这个公司是一个软件供应商,专门从事企业知识管理应用程序的 XML 解决方案方面的咨询工作。Fourthought 开发了 XML 中间件的开放源代码平台 4Suite和 4Suite Server。Mr. Ogbuji 是一位出生于尼日利亚的计算机工程师和作家,在美国科罗拉多州的博耳德(Boulder)工作、居住。您可以通过 uche.ogbuji@fourthought.com与 Mr. Ogbuji 联系。 |