内容


Python Web 服务开发者 第 6 部分

Python SOAP 库,第 2 部分

有与没有 WSDL

Comments

系列内容:

此内容是该系列 # 部分中的第 # 部分: Python Web 服务开发者 第 6 部分

敬请期待该系列的后续内容。

此内容是该系列的一部分:Python Web 服务开发者 第 6 部分

敬请期待该系列的后续内容。

这个月,我们来继续我们对用于 Python 的 SOAP 实现的研究。事实上,我们将仔细研究的是我们上次根本没有提到的一个东西:Zolera SOAP Infrastructure(ZSI)。ZSI 是 Rich Salz 的智慧之作,他是 Python/XML 包的积极贡献人,也是因特网基础架构和安全方面的几个重要标准的拟定工作的行家里手。ZSI 已经被添加为 SourceForge 上的 Python Web Services 工程的一部分,SourceForge 还托管了 SOAPy,它是另一个 Python/SOAP 包,我们在前一部分讨论了它。 我们希望,这种令人迷惑的情况能很快得到解决,解决的办法可能就是合并 SOAP 包。

ZSI 是一个纯粹的 SOAP 库。特别地,它较少涉及对任何底层协议细节的处理:它处理的是数据流,这个流必须是适当格式的 SOAP 消息,ZSI 根据解析后的数据把事件分派到 Python 事件处理程序。这种严格聚焦的优点是,ZSI 可能是最完全的、兼容性最好的、用于 Python 的 SOAP 库。 其缺点是,ZSI 用户必须做更多的工作才能让他或她的 Web 服务运转起来。

安装 ZSI

要安装本文所涉及的、最新版本的 ZSI 1.1,您需要 Python 2.0 或更新的版本。安装说明规定需要 PyXML 0.6 或更新的版本,但实际上您需要的是版本 0.6 或更新,但 早于 0.7 的版本。 PyXML 0.7 中出现了几个重要的 API 变化,它们不符合 ZSI 1.1。在处理依赖于 PyXML 的包时,您通常会遇到这种令人头疼的问题:如果您在使用声称依赖于 PyXML 0.6 的包时遇到了奇怪的错误,那么可想而知,您可能是使用了 PyXML 最新可用的版本,但不幸的是,这个最新版本不管用。最近的 CVS 活动似乎表明,ZSI 的下一个发行版可能会支持 PyXML 0.7 及更新的版本。请从 SourceForge 获取 PyXML 0.6.6 并将它解包,然后构建它:

$ python setup.py install

请下载 ZSI 包并将它解包,然后以相同的办法构建它:

$ python setup.py install

这是 Python 的分发实用程序(distribution utility)(也称为 distutils)给安装附加的 Python 包带来的一致性。

既然都安装好了,就请您使用 Python 文档工程中所用的相同的 LaTeX 工具来查看一下 doc 目录,其中有 HTML、PDF、postscript 和 TeX 格式的很不错的文档。

调用客户机特权

首先,我们来研究作为 SOAP 客户机的 ZSI。在上一部分中,我们编写了一个访问玩具 Web 服务的 SOAP.py 客户机,该 Web 服务返回 Captain Haddock(阿道克船长)的咒语(curse)(在 丁丁漫画迷中常盛不衰)。我们将用 ZSI 编写一个针对同一个服务器的客户机。不幸的是,我们在这样做时碰到了一些纠缠难解的问题。最简单的办法如 清单 1 的代码所示,但我们发现这种办法要求服务器既要按照参数名,又要按照参数的顺序来处理消息参数,而用来实现 Captain Haddock 服务器的 Borland Delphi 实现的灵活性却没这么好,因此,这让人很苦恼。

清单 1:试图访问 Captain Haddock SOAP 服务的 ZSI 程序
  #http://xmethods.net/detail.html?id=175
import sys
#Import the ZSI client
from ZSI.client import Binding
u = '/scripts/Haddock.exe/soap/IHaddock'
n = 'urn:HaddockIntf-IHaddock'
b = Binding(url=u, ns=n, host='www.tankebolaget.se', port=80,
            tracefile=sys.stdout)
try:
  lang = sys.argv[1]
except IndexError:
  lang = 'us'
result = b.Curse(lang)
print 'What captain Haddock had to say: "%s"'%result

因为这种简单的办法在许多 SOAP 1.1 服务器上都行得通,所以,尽管它不能给出我们期望的结果,我们还是对它稍作研究。首先,我们导入 ZSI 客户机模块和 Binding 类,这个类实现建立和调用 SOAP 请求的机制。 接着,我们建立 SOAP 端点的地址的组件。注意,我们分开建立主机、URL 路径和端口。对于 SOAP.py ,您可以使用单个 URL,但在这里却不行。这些组件用来创建与远程服务器的绑定。

最后,我们调用 binding 对象的 Curse 方法来发送远程请求,在方法的返回中获得结果。 不过,正如我们提到过的,这种简单的办法在 Captain Haddock 服务器上行不通。我们必须使用一种更复杂的结构来把 LangCode 参数传递给该服务器。 清单 2中的代码可以做到这一点。

清单 2:能够实现对 Captain Haddock SOAP 服务的访问的 ZSI 程序
  import sys
#Import the ZSI client
from ZSI import TC
from ZSI.client import Binding
u = '/scripts/Haddock.exe/soap/IHaddock'
n = 'urn:HaddockIntf-IHaddock'
b = Binding(url=u, host='www.tankebolaget.se', port=80, ns=n)
try:
  lang = sys.argv[1]
except IndexError:
  lang = 'us'
class CurseRequest:
    def __init__(self, langCode):
        self.LangCode = langCode
CurseRequest.typecode=TC.Struct(CurseRequest,
                                [TC.String('LangCode')],
                                'Curse',
                                inline=1)
try:
    result_list = b.RPC(u, 'Curse', CurseRequest(lang), TC.Any(aslist=1))
    #Extract the first returned parameter
    result = result_list[0]
    print 'What captain Haddock had to say: "%s"'%result
except:
    raise
    print 'reply=', b.reply_code
    print 'reply_msg=', b.reply_msg
    print 'headers=', b.reply_headers
    print 'data=', b.data

TypeCode 模块能够实现 Python 数据类型和 SOAP 数据类型之间既精确又可扩展的映射。它有一组用于 StringInteger 等等的内置类。它还定义了一个 Struct 类,用来定义任意的数据聚合,此外,还定义了特殊的类 Any ,它可以用来表示任何内置或导出类型,因而它也就成了动态类型化(dynamic typing)的基础。其中后两个类是解决 SOAP 编码中的许多互操作性问题的关键,这些问题源于类型化数据的不同的数据编入和数据编出。虽然要使 ZSI 能在我们正在处理的 Delphi 服务器中使用,我们就必须解决一些复杂的难题,但是,ZSI 至少提供了做到这一点所需的所有机制。对于其它 Python SOAP 实现,要解决互操作性问题,我们得修改客户机库代码。

接下来,我们创建 CurseRequest 类,它将被数据编入成 Captain Haddock 服务器所要求的形式。我们为所要求的 LangCode 参数定义一个实例属性。然后,把一段 ZSI 代码指定给该类,这段代码将 LangCode 值数据编入成字符串。 要注意的一点是 inline=1 规范,它禁用了使用多引用(multi-reference)值进行数据编入。在缺省情况下,ZSI 使用多引用对结构进行数据编入,这意味着,为了避免值的重复,您可以在 SOAP 消息的一个部件中表达某个值,然后通过在该 SOAP 消息的另一个部件中进行引用来引用该同一个值。SOAP 编码多引用值不如简单值那么常用,所以,ZSI 的结构类型在缺省情况下被数据编入成多引用,就让人觉得有点奇怪了。

这一细节及其它一些东西似乎强化了 ZSI 的作者 Rich Salz 给我们的忠告:ZSI 实际上主要是为复杂的 Web 服务而设计的。他承诺会尽快使在玩具类服务上使用 ZSI 变得容易些,此类服务似乎占了已发布服务的大部分。

以下代码说明了示例客户机代码的用法:

$ python curse-zsi.py 
What captain Haddock had to say: "Polygraphs!"

在 ZSI 中启用调试和跟踪相当容易。只要把 tracefile=sys.stdout 参数传递给 Binding 初始化器就可以了。这样修改之后,样本 ZSI 会话看起来与此类似:

$ python curse-zsi-trace.py 
_________________________________ Thu Jan 31 10:25:09 2002 REQUEST:
<SOAP-ENV:Envelope
 xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
 xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:xsd="http://www.w3.org/2001/XMLSchema"
 xmlns:ZSI="http://www.zolera.com/schemas/ZSI/" >
<SOAP-ENV:Body>
<Curse id="822b6dc">
<LangCode id="81089d0" xsi:type="xsd:string">us</LangCode>
</Curse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
_________________________________ Thu Jan 31 10:25:10 2002 RESPONSE:
Server: Microsoft-IIS/5.0
Date: Thu, 31 Jan 2002 17:18:37 GMT
Content-Type: text/xml
Content-Length: 524
Content:
<?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.xmlsoap.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">Sea-lice!</NS1:return>
</NS1:CurseResponse>
</SOAP-ENV:Body></SOAP-ENV:Envelope>
What captain Haddock had to say: "Sea-lice!"

您正在接受服务吗?

建立一个 ZSI 服务器相当简单。 清单 3提供了一种非常简单的日历 SOAP 服务器,这个日历 SOAP 服务器与我们在上一期专栏中作为 SOAP.py 的示例而使用的相同。(请参阅 参考资料。)

清单 3:ZSI 日历 SOAP 服务器
  #!/usr/bin/env python
import sys, calendar
#Import the ZSI machinery
from ZSI import dispatch
def getMonth(year, month):
  return calendar.month(year, month)
def getYear(year):
  return calendar.calendar(year)
print "Starting server..."
dispatch.AsServer(port=8080)

请注意,它比用 SOAP.py 实现还要容易。您所做的就是为每个方法定义一个函数。有了所需的参数,变量数和关键字参数还可以作为位置参数和名称参数使用。 dispatch.AsServer() 调用只是把所有已定义的函数注册成 SOAP 方法,并在指定端口启动 HTTP 服务器。

有一个问题是,ZSI 的服务器代码似乎不能轻易地纠正请求中所用的名称空间。其文档声称有一个 dispatch.GetNS() 函数,它返回请求元素中所用的名称空间,但事实好像并非如此。这是一个极其严重的缺失,因为在请求中所用的名称空间是请求中的基本部件。

以下 ZSI 客户机代码用来练习我们刚才编写的日历:

清单 4:ZSI 日历 SOAP 客户机
  #http://xmethods.net/detail.html?id=175
import sys
#Import the ZSI client
from ZSI.client import Binding
u = ''
n = 'http://uche.ogbuji.net/eg/ws/simple-cal'
b = Binding(url=u, ns=n, host='localhost', port=8080)
result = b.getMonth(2002, 2)
print result[0]
result = b.getYear(2002)
print result[0]

要测试它,只需在一个控制台中使用“$ python calendar-zsi.py”启动服务器,接着,在另一个控制台中就会出现:

$ python2.1 cal-client.py 
February 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
2002
       January                  February                    March
Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su
    1  2  3  4  5  6                   1  2  3                   1  2  3
 7  8  9 10 11 12 13       4  5  6  7  8  9 10       4  5  6  7  8  9 10
14 15 16 17 18 19 20      11 12 13 14 15 16 17      11 12 13 14 15 16 17
21 22 23 24 25 26 27      18 19 20 21 22 23 24      18 19 20 21 22 23 24
28 29 30 31               25 26 27 28               25 26 27 28 29 30 31
        April                      May                      June
Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su
 1  2  3  4  5  6  7             1  2  3  4  5                      1  2
 8  9 10 11 12 13 14       6  7  8  9 10 11 12       3  4  5  6  7  8  9
15 16 17 18 19 20 21      13 14 15 16 17 18 19      10 11 12 13 14 15 16
22 23 24 25 26 27 28      20 21 22 23 24 25 26      17 18 19 20 21 22 23
29 30                     27 28 29 30 31            24 25 26 27 28 29 30
        July                     August                   September
Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su
 1  2  3  4  5  6  7                1  2  3  4                         1
 8  9 10 11 12 13 14       5  6  7  8  9 10 11       2  3  4  5  6  7  8
15 16 17 18 19 20 21      12 13 14 15 16 17 18       9 10 11 12 13 14 15
22 23 24 25 26 27 28      19 20 21 22 23 24 25      16 17 18 19 20 21 22
29 30 31                  26 27 28 29 30 31         23 24 25 26 27 28 29
                                                    30
       October                  November                  December
Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su      Mo Tu We Th Fr Sa Su
    1  2  3  4  5  6                   1  2  3                         1
 7  8  9 10 11 12 13       4  5  6  7  8  9 10       2  3  4  5  6  7  8
14 15 16 17 18 19 20      11 12 13 14 15 16 17       9 10 11 12 13 14 15
21 22 23 24 25 26 27      18 19 20 21 22 23 24      16 17 18 19 20 21 22
28 29 30 31               25 26 27 28 29 30         23 24 25 26 27 28 29
                                                    30 31

结束语

我们在上一期专栏中根本没有提及的 ZSI 实际上是用于 Python 的最成熟最有用的 SOAP 库。笔者希望,能够尽快得到可以解决我们在文中所抱怨的某些问题的版本 1.2。

既然我们已经粗略了解了各个 Python SOAP 包,并且仔细研究了其中几个,那么,在下一部分中,我们就来看看 Python SOAP 实现之间的互操作的工作原理。


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=SOA and web services
ArticleID=20856
ArticleTitle=Python Web 服务开发者 第 6 部分: Python SOAP 库,第 2 部分
publish-date=02012002