IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope: Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  SOA and Web services  >

Python Web 服务开发者: Python SOAP 库,第 5 部分

SOAPpy 的新开发

developerWorks
文档选项

未显示需要 JavaScript 的文档选项


级别: 初级

Scott Archer (scott.archer@glowingorb.com), 软件架构师, GlowingOrb, Inc.
Uche Ogbuji (uche.ogbuji@fourthought.com), 首席顾问, Fourthought, Inc.

2004 年 4 月 01 日

如同它的姊妹项目 ZSI 一样,SOAPpy 近来逐渐活跃,现在已经是版本 0.11.3。这个版本包括 WSDL 支持和很多其他的改进。Uche Ogbuji 和 Scott Archer 试用了这个新的版本(他们使用的 Web 服务与以前曾通过 ZSI 1.4.1 访问的 Web 服务的复杂程度是一样的),并且碰到了一些不同的困难。

在前两期中(请参阅 参考资料),我们了解了最新的 ZSI,也就是过去介绍的 Python Web 服务库。这个月,我们将着眼于另外一个这样的库的更新。SOAPpy 是“Web Services for Python”项目的另外一个组件。版本 0.11.3 是最新的,并且如同 ZSI,最新的一组版本添加了一系列给人印象深刻的改进和补充。我们同样也已经在我们最近关于实际的 Web 服务的文章中使用 SOAPpy 来访问 Google 和 Amazon 的 Web 服务。(请参阅 参考资料)。

安装软件

我们下载了在 Python 2.3.3 中安装的 SOAPpy 0.11.3,但是在安装它以前,我们必须下载并安装下列必备软件:

  • pyXML0.8.3 或更新的版本同样也是必需的。

可以使用当前标准的 python setup.py install 从解压缩目录很好地安装这个软件。您也可以安装下面的软件以便启用特殊的功能。然而,我们没有选择安装这个软件。

  • pyGlobus用于启用对用于网格计算的 Globus Alliance toolkit 的支持(目前是 Globus toolkit 的 2.2.4 和 2.4 版本必不可少的一部分)。
  • M2Crypto.SSL用来启用对 SOAP 服务器上的 SSL 的支持。为了启用 SOAP 客户端对 SSL 的支持,您需要 SSL 支持编译成 Python。这是 Python 2.3+ 缺省设置的。




回页首


WSDL 和更多的 dateTime 问题

与 ZSI 一样,SOAPpy 最近的特征是 WSDL 支持。在 SOAPpy 中用于 SOAP 访问底层 API 是相当容易使用的,但是 WSDL 承诺使用更少的建立(set-up)代码。

我们尝试调用一个 Web 服务,这个 Web 服务使用 ZSI 1.4.1 会给我们造成一些困难。正如我们在上篇文章中(Python SOAP 库,第 4 部分)所介绍的:


Richard Hastings 的 Air Fare Quote Search 是在 Apache Axis 中实现的,并且实时地查询一些航空公司的 Web 站点来寻找特定航线最合适的班机票价(请参阅 参考资料)。它聚集并返回根据价钱排序的结果。这个 WSDL 位于 http://wavendon.dsdata.co.uk:8080/axis/services/SBGGetAirFareQuote?wsdl。它定义了两个操作: getAirFareQuotegetAirlines 。前者用来执行价钱查询,它有四个参数:两个 W3C XML Schema Language Data Types (WXSDT) dateTime 值(给出航班大致的起飞和返回时间)以及两个 WXSDT string 值(给出飞行起点和终点之间飞机场的三个字母的代码)。

建立(set-up)代码是足够直接了当的:

>>> import SOAPpy
>>> wsdl = 'http://wavendon.dsdata.co.uk:8080\\
... /axis/services/SBGGetAirFareQuote?wsdl'
>>> proxy = SOAPpy.WSDL.Proxy(wsdl)
      
                

我们尝试直接的位置参数方法调用,我们已经使用 ZSI 试过。我们已经输入了两个 dateTime 参数,在两个字符串参数之后,正好用于 getAirFareQuote 方法调用。所以,我们已经解决了如何创建 dateTime 类型的问题。文档 docs/simpleTypes.txt 建议创建 SOAP.DateTime 的实例,但是这给出了 AttributeError 。结果,我们不得不使用 SOAP.dateTimeType 。这采用一个 Python 时间元组(time tuple)。为了可读性,我们由 ISO-8601 字符串创建这个元组,并且挑选参数为我们提供即将去美国华盛顿(Washington,D.C.,USA)的 PyCon 旅行的费用。

>>> proxy.getAirFareQuote(SOAPpy.dateTimeType(dep), SOAPpy.dateTimeType(ret), 'den', 'phl')
>>> import time
>>> ISO_8601_DATETIME = '%Y-%m-%dT%H:%M:%S'
>>> dep = time.strptime('2004-03-24T12:30:59', ISO_8601_DATETIME)
>>> dep_dt = SOAPpy.dateTimeType(dep)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "/home/uogbuji/lib/lib/python2.3/site-packages/SOAPpy/Types.py", line 79, in __init__
    self._data = self._checkValueSpace(data)
  File "/home/uogbuji/lib/lib/python2.3/site-packages/SOAPpy/Types.py", 
    line 442, in _checkValueSpace
    raise ValueError, "invalid %s value - %s" % (self._type, e)
ValueError: invalid dateTime value - invalid type
>>> 
      
                

在调试这个相当隐讳的错误的试验和出错之后,我们了解到 SOAPpy 并不像 Python 时间元组(time tuples)在 tm_isdst 字段带有 -1 。在使用这个值的时候,它通常说明用户想要用这个库来处理使用敏感的缺省夏令时(daylight savings time)。因为 SOAPpy 不能处理 Python 时间元组(time tuples),我们不得不强制所有的都遵从格林尼治标准时间(Greenwich Mean Time,GMT)。





回页首


我们需要一些结构

在将日期调整为使用 GMT 以后,我们最终能够与远程服务器联系:

>>> import time
>>> dep = SOAPpy.dateTimeType((2004, 3, 24, 12, 30, 59, 4, 86, 0))
>>> ret = SOAPpy.dateTimeType((2004, 3, 26, 12, 30, 59, 4, 86, 0))
>>> proxy.getAirFareQuote(dep, ret, 'den', 'iad')
        
          ... Long, bewildering SOAP fault snipped ...
        
                
      

Python 的 time.strptime 已经成为很多开发人员想当然滥用的主题,它没有办法处理合适的 ISO 8601 时区间隔。最接近的格式指定符 %Z 只接受国内的时区名称,比如 MST,这在 ISO-8601 中是不允许的。最后,我们仅仅是直接使用麻烦的时间元组(time tuples)。我们曾经构造了一个 SOAP 消息并发送给了服务器。在最近本文发表之前,我们还不能使用 ZSI 来这样做( 单击这里以获得更新)。不幸的是,服务器并不那么热心的。它发送回一个极其难懂的 SOAP 错误--一个巨大的 Java 堆栈跟踪,其中的关键行是: org.xml.sax.SAXException: No such operation 'getAirFareQuote。我们知道这个错误消息有一些可疑,因为我们可以使用在线的 SOAP 调试器来访问 getAirFareQuote 方法。实际上,如果我们尝试做一些实验:

>>> proxy.getAirFareQuote()
[]
>>>
      
                

我们确实得到了结果。错误消息是“我不理解您调用这个方法的方式”,这是一种难以理解的表述。通过 Google 检索的一些资料带来这样一条线索,就是我们应该更进一步地阅读 WSDL。服务器其实期望在称作 in0 的结构中定义多引用(multi-ref),包括我们已经直接传送的四个参数。所以,我们需要处理结构类型。文档 docs/complexTypes.txt 对于寻找如何完成这项工作的方法根本没有帮助。我们不得不钻研 SOAPpy 代码来寻找我们所需要的合适的类, SOAPpy.structType() 。我们做了这样一些实验:

>>> in0 = SOAPpy.structType()
>>> in0._addItem('outwardDate', dep)
>>> in0._addItem('returnDate', ret)
>>> in0._addItem('originAirport', 'den')
>>> in0._addItem('destinationAirport', 'iad')
      
                

这次我们得到了不同的 SOAP 错误--一个 Java Traceback,带有初始消息 org.xml.sax.SAXException: No deserializer defined for array type {http://www.w3.org/1999/XMLSchema}ur-type。现在是查看 WSDL 实际上在线路上发送什么的时候了。我们启动 SOAPpy 的调试工具。幸运的是,与 SOAPpy 的 0.9x 版本不同,有一种简单的开启和关闭调试代码的方法,即将 SOAPpy.Config.debug 设置为 0 或 1。

>>> SOAPpy.Config.debug = 1
>>> proxy.getAirFareQuote(in0)
      
                

这引出大量的文本,但是我们发现出站的 SOAP 负载隐藏在输出中。

*** Outgoing SOAP ******************************************************
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope
  SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
  xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
  xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
  xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
  xmlns:xsd3="http://www.w3.org/2001/XMLSchema"
  xmlns:xsd="http://www.w3.org/1999/XMLSchema">
<SOAP-ENV:Body>
<ns1:getAirFareQuote
  xmlns:ns1="urn:SBGAirFareQuotes.sbg.travel.ws.dsdata.co.uk"
  SOAP-ENC:root="1">
<v1 href="#i1"/>
</ns1:getAirFareQuote>
<xsd:v1 id="i1" SOAP-ENC:root="0">
<outwardDate href="#i2"/>
<returnDate href="#i3"/>
<originAirport href="#i4"/>
<destinationAirport href="#i5"/>
</xsd:v1>
<outwardDate SOAP-ENC:arrayType="xsd:ur-type[4]" xsi:type="SOAP-ENC:Array"
             SOAP-ENC:root="0" id="i2">
<item href="#i3"/>
<item href="#i3"/>
<item href="#i4"/>
<item href="#i5"/>
</outwardDate>
<returnDate xsi:type="xsd3:dateTime" id="i3"
            SOAP-ENC:root="0">2004-03-24T12:30:59Z</returnDate>
<originAirport xsi:type="xsd:string" id="i4"
            SOAP-ENC:root="0">den</originAirport>
<destinationAirport SOAP-ENC:arrayType="xsd:string[2]"
            xsi:type="SOAP-ENC:Array" SOAP-ENC:root="0" id="i5">
<item href="#i6"/>
<item href="#i7"/>
</destinationAirport>
<item xsi:type="xsd:string" id="i6" SOAP-ENC:root="0">phl</item>
<item xsi:type="xsd:string" id="i7" SOAP-ENC:root="0">iad</item>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
************************************************************************
      
                

出于编排格式方面的原因,我们已经添加了一些新行和缩进。很明显,这完全是添乱。W3C XML Schema 名称空间和基本数据结构来自于 SOAP 提议的废弃很久的版本。很难想象在没有大量工作的情况下,如何将它转换成当前 SOAP 实现可以识别的事物。搜索 SOAPpy 代码以查找可能有帮助的标记和选项并没有找到任何鼓舞人心的东西。此刻,我们放弃了试图访问飞机票价报价服务。当然,我们可以足够容易地访问 getAirlines 方法:

>>> SOAPpy.Config.debug = 0
>>> proxy.getAirlines()
['Alitalia', 'American Airlines', 'BMI', 'BMIBaby', 'British Airways',
 'Continental', 'EasyJet', 'EBookers', 'Expedia', 'Global Traveller',
 'Lufthansa', 'Northwest Airlines', 'Maersk Air', 'Opodo', 'Qantas',
 'Ryanair', 'Star Alliance', 'Travelocity', 'United']
>>> 
      
                

当然,这又回到了我们曾经希望越过的 Web 服务的微不足道的特点。WSDL 模块 SOAPpy 和 ZSI 受欢迎的增加,因为它简化了解决服务的底层细节的流程。然而,如果您需要复杂的数据结构编组,而 Python SOAP 实现好像还没有跟上的时候,它并没有任何帮助。此外,我们想要强调,这是对 SOAP RPC 复杂类型极端神秘的控诉。这是整个 SOAP 社区急待解决的问题。幸运的是,随着 SOAP 的文档/文字(document/literal)特点的出现,已经有了一些希望。





回页首


ZSI 更新

在我们上篇文章发表之后,ZSI 的核心开发人员 Rich Salz 发来一封电子邮件指出:


有时, time.strptime() 的返回值会发生从元组到 <type 'time.struct_time'> 的改变。[...]下面的片断--注意 tuple() 调用 dep 和 ret --[达到]更进一步。
ZSI bug?Python 发展得太迅速?你的理由 ...

我们试验了他提出经过修改的代码:

>>> from ZSI import ServiceProxy
>>> wsdl = 'http://wavendon.dsdata.co.uk:8080\\
... /axis/services/SBGGetAirFareQuote?wsdl'
>>> proxy = ServiceProxy(wsdl)
>>> import time
>>> ISO_8601_DATETIME = '%Y-%m-%dT%H:%M:%S'
>>> dep = tuple(time.strptime('2003-12-06T12:30:59', ISO_8601_DATETIME))
>>> ret = tuple(time.strptime('2003-12-12T12:30:59', ISO_8601_DATETIME))
>>> proxy.getAirFareQuote(dep, ret, 'den', 'phl')
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "/home/uogbuji/lib/lib/python2.3/site-packages/ZSI/ServiceProxy.py", 
    line 82, in __call__
    return self.parent()._call(self.__name__, *args, **kwargs)
  File "/home/uogbuji/lib/lib/python2.3/site-packages/ZSI/ServiceProxy.py", 
    line 65, in _call
    apply(getattr(binding, callinfo.methodName), args)
  File "/home/uogbuji/lib/lib/python2.3/site-packages/ZSI/client.py", line 28, in __call__
    requesttypecode=TC.Any(self.name, aslist=1))
  File "/home/uogbuji/lib/lib/python2.3/site-packages/ZSI/client.py", line 132, in RPC
    return self.Receive(replytype, **kw)
  File "/home/uogbuji/lib/lib/python2.3/site-packages/ZSI/client.py", line 261, in Receive
    raise FaultException(msg)
ZSI.FaultException: org.xml.sax.SAXException: No such operation 'getAirFareQuote'
>>> 
      
                

使用经过修改的代码使我们到了将 SOAP 请求传送给服务器的时刻,但是我们回到了解决如何发送远程方法期望的精确的结构。





回页首


总结

尽管我们访问飞机票价 Web 服务的努力是令人沮丧的,但是我们可以看到 ZSI 和 SOAPpy 取得了重大的进展。在这些文章中我们的目的并不是贬低这些建议,而是展示我们落入的陷阱,以便其他人可以避开它们。我们认为这里的问题很大一部分在于,当您走出平凡 Web 服务领域时,SOAP 就太复杂了。正如我们提到的,人们逐渐接受的文档/文字(document/literal)编码将数据体系结构的问题缩小到我们在 XML 中已经得到了很好的理解的问题。幸运的是,您可以实现很多,而又不用闯入混乱的复杂结构类型。在最近的文章中,我们已经成功地使用 SOAPpy (而且某些测试说明 ZSI 也将起作用)访问了 Google 和 Amazon.com 的 Web 服务 API。甚至这些商业服务都避免复杂的结构类型。飞机票价报价服务仅仅接受简单的定位参数是没有理由的。对于遇到的大部分 SOAP 任务,您应该能够使用 ZSI 和 SOAPpy,但是认识到它们的局限也很重要。



参考资料



作者简介

Scott Archer 是 GlowingOrb 公司的软件架构师和联合创立者、专注于模型驱动的解决方案和将它们集成到核心业务流程的软件工具开发者。Archer 拥有 University of Hong Kong 计算分子生物学哲学硕士学位。您可以通过 scott.archer@glowingorb.com与 Archer 联系。


Uche Ogbuji 的照片

Uche Ogbuji 是 Fourthought Inc.的顾问和联合创立者、软件经销商和专注于企业知识管理的 XML 解决方案方面的顾问。Fourthought 开发了 4Suite,一个用于 XML、RDF 和知识管理应用程序的开放源代码平台。Mr. Ogbuji 也是 VersaRDF 查询语言的主要开发者。他是计算机工程师和作家,生于 Nigeria,生活和工作在 Boulder,Colorado,USA。您可以通过 uche.ogbuji@fourthought.com与 Ogbuji 联系。




对本文的评价










回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款