Python中SOAP项目的介绍及其在web开发中的应用
SOAP.py 客户机和服务器
SOAP.py 包含的是一些基本的东西。没有 Web 服务描述语言(Web Services Description Language,WSDL)或者任何其它附加的东西,只有用 Python 实现的 SOAP 客户机和服务器的透明支持。甚至这个包中的一个很好的功能也只是与基础架构相关:SOAP.py 支持安全套接字层(SSL)用于加密的 SOAP 传输。为使用这个功能,您必须安装 M2Crypto,M2Crypto 是一个库,包含各种加密工具和格式,从 RSA 和 DSA 到 HTTPs、S/MIME 等等。在这一部分,我们不准备讨论 SOAP.py 的 SSL 支持。
SOAP 操作摘要
目前为止,SOAP 实用程序好象仍是比较流行的使用 Python 的开放源代码活动。下面是该项目的纲要以及它们目前的状态。首先,参与者:
- 4Suite SOAP,由 Fourthought 管理
- SOAPy,由 Adam Elman 管理
- SOAP.py,Python 项目的一个 Web 服务项目
- soaplib,由 Secret Labs 管理
- Orchard,由 Ken MacLeod 管理
- PySOAP,由 Dave Warner 管理
4Suite SOAP 是我们自己的实现,我们在本专栏的前面三部分中使用过(请参阅 参考资料以获得它的链接)。它目前仍在开发中。
SOAPy 是在 2001 年 4 月公布的,目前处于 alpha 的预备阶段,但现在好象停止开发了。
SOAP.py 开发被冻结了。SOAP.py 这个项目是由 actzero 公司赞助的,而 actzero 却不再从事这一行业了。正在邀请自愿开发/维护 SOAP.py 的组织。
soaplib 的开发好象也延缓了,考虑到 Secret Labs 这段时间所承担的大量工作,或许就可以理解为什么会这样了。这个瑞典的公司是由 Fredrik Lundh 掌管的,他在 Python 圈内是出名的“工作狂”,同时也是 Python Association 董事会的一名成员。Secret Labs 还开发 PythonWare(Python 的一个核心和重要的附加模块);PythonWorks(一个领先的 Python IDE);Python Imaging Library 和许多其它好东西(日常 Python-URL Web 日志就是其中的一部分)。
Orchard 是一个数据管理框架,基本上是一种用一个公共接口管理不同数据格式的方法。它实现了一个 SOAP 客户机作为在远程过程调用中向 SOAP 服务器发送 Orchard 数据项的基本方法(被称为节点)。
PySOAP 这个项目主要是想作为 Dave Warner 的 Church 管理套件的一部分,但它还从没发行过任何文件,好象是一个毫无生气的项目。
安装
开始先下载分发包(在写这篇文章的时候,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 服务器
用 SOAP.py 实现 SOAP 服务器相当容易。作为一个示例,我们将仿建字段,还要实现一个很常见的服务:一个程序,给出年份和月份,它将以字符串的形式打印出日历。它的程序服务器是 清单 2(calendar-ws.py)。
清单 2:实现日历服务器的 SOAP.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中。
清单 3:用 Python 核心库写的访问日历服务的客户机
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 reply_body = requestor.getfi-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() le().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 总结
我们已经注意到了,虽然 SOAP.py 的互操作性还存在一些问题,但可用的调试工具可望提供帮助。