 | 级别: 初级 Mike Olson (molson@fourthought.com), 首席执行官, Fourthought
2001 年 6 月 01 日 这篇文章是关于构建作为 Web 服务实现的软件资源库的第三篇,也即最后一篇文章,Mike Olson 和 Uche Ogbuji 将在这里进一步扩展软件资源库,以使用 WSDL 宣告它的存在。
在关于创建资源库的这个系列的第二部分结束时,我们提供了使用 HTTP Post 方法和 SOAP 向资源库上载软件的方法,并在此停了下来。在这个关于构建软件资源库的三部分系列的第三部分中,我们将向您展示如何扩展软件资源库,以使用 WSDL 宣告它的存在,并给出一个最终的示例程序将所有部分连接起来。
准备工作
在继续往下阅读文章之前,您应该对有关协议和该软件最新的变化有一些了解。首先,您应该熟悉 Web 服务描述语言(Web Services Description Language,WSDL)的工作原理(请参阅
参考资料,那里有解释 WSDL 工作原理的文章)。
本专栏中的示例需要 Python 1.5.2 或更新版本(尽管推荐的是 Python 2.1)、4Suite 版本 0.11.1 或者更新版本、4Suite Server 版本 0.11.1 或更新版本以及 WSDL 库的支持。本专栏的参考资料部分有关于下载和安装所有上面要求的软件的信息。
4Suite Server 0.11.1 中有一种新特性,那就是加入了一组对 Python 的 distutils 包的扩展,使您可以直接从产品的 setup.py 文件启动 4Suite Server 安装。本文提供了对 setup.py 文件的下载,该文件将向您举例说明其中的原理。最终的结论是,要安装和启动示例应用程序,您只需运行 setup.py 就可以了。
使用 WSDL
WSDL 是一种用来描述 Web 服务技术特征的 XML 格式。将它与 UDDI(Universal Description,Discovery,and Integration,统一描述、发现和集成)相结合,就可以让 Web 服务宣告其存在和功能。使用这些技术,我们就有可能让应用程序查询 UDDI 资源库,并查找关于所需 Web 服务的信息,然后对该 Web 服务发出请求。
因为 UDDI 本身就要占用整整一个系列的文章来描述(那就请关注本专栏,等待这方面的内容吧),所以我们将只集中描述 WSDL 方面的内容。为了向软件资源库添加 WSDL,我们将创建一个简单的文档定义,用来记录关于 WSDL 文档存在的简单语句。
清单 1展示了 WSDL 文档定义。
清单 1:WSDL 文档定义
<DocDef xmlns='http://namespaces.4suite.org/4ss' name='WSDL'>
<RdfMapping>
<Subject>$uri</Subject>
<Predicate>'http://schema.4suite.org#wsdl'</Predicate>
<Object>/wsdl:definitions/@name</Object>
</RdfMapping>
<NsMapping>
<Prefix>wsdl</Prefix>
<Uri>http://schemas.xmlsoap.org/wsdl/</Uri>
</NsMapping>
</DocDef>
|
该文档定义会在资源描述框架(Resource Description Framework,RDF)模型中为添加到资源库的每一个 WSDL 文档添加一条语句。我们的 WSDL POST 处理程序将使用这条语句来跟踪系统中所有的 WSDL 文档。
WSDL 处理程序
我们在本系列的上一部分(请参阅
参考资料)中花了相当多的时间解释如何使用 4Suite Server POST 处理程序来向系统添加新的内容。我们将以这些知识为基础,编写一组 WSDL 注册页,让您可以添加、编辑和删除 WSDL 描述。
安装了上述代码之后,请您启动 4Suite Server HTTP 服务器,这样我们就可以一起研究示例应用程序了。
清单 2展示了您需要确定已经在 4Suite Server 配置文件中的 RDF 描述。
清单 2: SOAP 处理程序的 RDF 描述
<rdf:Description ID='SoftRepoSoapHandler'>
<rdf:type resource='http://xmlns.4Suite.org/4ss/properties#HttpHandler'/>
<Priority>30</Priority>
<Module>WebServices4.SoftRepoSoapHandler</Module>
</rdf:Description>
<rdf:Description ID='SoftRepoWsdlHandler'>
<rdf:type resource='http://xmlns.4Suite.org/4ss/properties#HttpHandler'/>
<Module>WebServices4.WsdlHandler</Module>
</rdf:Description>
<rdf:Description ID='SoftRepoGetHandler'>
<rdf:type resource='http://xmlns.4Suite.org/4ss/properties#HttpHandler'/>
<Module>FtServer.Protocols.Http.GetHandler</Module>
<DocumentRoot>/WebServices-4</DocumentRoot>
</rdf:Description>
<rdf:Description ID='SoftRepoPostHandler'>
<rdf:type resource='http://xmlns.4Suite.org/4ss/properties#HttpHandler'/>
<Module>FtServer.Protocols.Http.PostHandler</Module>
<DocumentRoot>/WebServices-4</DocumentRoot>
</rdf:Description>
<rdf:Description ID='SoftRepoDeleteHandler'>
<rdf:type resource='http://xmlns.4Suite.org/4ss/properties#HttpHandler'/>
<Module>FtServer.Protocols.Http.DeleteHandler</Module>
<DocumentRoot>/WebServices-4</DocumentRoot>
</rdf:Description>
<rdf:Description ID='PythonSoftRepoServer'>
<rdf:type resource='http://xmlns.4Suite.org/4ss/properties#PythonServer'/>
<StartMode>MANUAL</StartMode>
<Port>8080</Port>
<PidFile>/tmp/PyHttp.pid</PidFile>
<ErrorLog>/tmp/PyHttp.all</ErrorLog>
<TransferLog>/tmp/PyHttp.all</TransferLog>
<Handler resource='#SoftRepoGetHandler'/>
<Handler resource='#SoftRepoWsdlHandler'/>
<Handler resource='#SoftRepoPostHandler'/>
<Handler resource='#SoftRepoDeleteHandler'/>
<Handler resource='#SoftRepoSoapHandler'/>
</rdf:Description>
|
然后,请按如下命令行启动服务器:
[molson@penny molson]$ 4ss_manager start PythonSoftRepoServer
PythonSoftRepoServer started (pid 6045)
|
配置了 4Suite Server 并使其运行之后,您就应该能够使浏览器定位于
http://localhost:8080/index.html以浏览 WSDL 管理器的索引页了。如
图 1所示,我们可以从索引页浏览已有的 WSDL 描述,也可以向系统添加新的 WSDL 描述。
因为系统中最初并没有 WSDL 描述,我们将添加一个。请单击
add new WSDL description链接。您将看到一个新页面,它会请求您输入一个新的 WSDL 描述。
清单 3(也在示例文件 data/softrepo.wsdl 中)展示了软件资源库的一个 WSDL 描述示例。
图 1:WSDL 管理器索引页
要向系统添加新的 WSDL,请将样本描述复制到文本输入框中。我们将消息张贴到 add.doc 和 add.xslt 文件,以在系统中生成 WSDL。因为我们希望添加的 WSDL 与所输入的完全一致,所以 XSLT 只是将 WSLT 变量
wsdl的值复制到输出上。请注意,我们必须将输出方法设置为 text,这样它才有正确的格式文本(而且字符实体不被转义)。
清单 4展示了 add.xslt 的内容。
清单 3:新的 WSDL 文档定义
<?xml version="1.0"?>
<definitions name="Software Repository"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:tns="http://4Suite.org/webservices-4.wsdl"
xmlns="http://schemas.xmlsoap.org/wsdl/">
<message name="AddSoftwareInput">
<part name="title"/>
<part name="creator"/>
<part name="home"/>
<part name="version"/>
<part name="description"/>
</message>
<portType name="AddSoftwarePortType">
<operation name="AddSoftware">
<input message="tns:AddSoftwareInput"/>
</operation>
</portType>
<binding name="SoftwareRepoBinding"
type="tns:AddSoftwarePortType">
<soap:binding style="document"
transport="http://schemas.xmlsoap.org/soap/http"/>
<operation name="Add">
<soap:operation soapAction="http://localhost/Add"/>
<input>
<soap:body use="literal"
namespace="http://spam.com/softrepo"/>
</input>
</operation>
</binding>
<service name="SoftwareRepoService">
<documentation>Example Software Repository Service</documentation>
<port name="SoftwareRepoPort" binding="tns:SoftwareRepoBinding">
<soap:address location="http://localhost/Add"/>
</port>
</service>
</definitions>
|
清单 4:add.xslt 文件
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0"
>
<xsl:output method='text'/>
<xsl:param name='wsdl'/>
<xsl:template match='/'>
<xsl:value-of select='$wsdl'/>
</xsl:template>
</xsl:stylesheet>
|
添加了 WSDL 之后,我们就会看到一个如
图 2所示的视图屏幕。这个视图与我们从索引页选择“View WSDL Definitions”之后将看到的一样。通过该视图,我们就能够从资源库编辑和删除 WSDL 定义了。(编辑和删除功能的原理相对来说比较简单,应该不需要另加解释。)
图 2:WSDL 管理器视图页
组合在一起
那么,我们现在有了一个小巧的软件资源库,我们知道如何使用 SOAP 连接到这个资源库,甚至可以使用 WSDL 设置和获取它的描述。让我们来编写一个使用所有这些技术的小程序来向系统添加软件。程序将在上个专栏中编写的应用程序基础上进行扩展,但是在这里,它将在资源库中查询可用的软件资源库服务。在查询结束时,它会提示用户输入所需信息,并向系统添加新的软件条目。
在 Web 服务领域中,UDDI 注册中心是存储 Web 服务的 WSDL 的地方。不幸的是,编写和描述一个合适的 UDDI 示例会占用专栏太多的篇幅,所以我们将使用我们自己的方法来发现资源库中的 Web 服务。
正确的方法是扩展 SOAP 处理程序,使其接受让我们查询可用的 Web 服务的新消息。然而,我们已经向您展示了如何在 4Suite Server 中编写 SOAP 处理程序,所以在本示例中,我们将对 HTTP 1.1 请求中所允许的方法进行扩展,以包括“WSDL”这种请求类型。这个请求需要一个头字段
name,它将成为我们要查询的 Web 服务的名称。
清单 5展示了一个定制 4Suite Server WSDL 处理程序的代码。
关于处理程序,我们要注意三件事情。首先,它是从 CommonHandler 继承而来的。这个基类将使编写处理程序类更加容易。它会添加很多常用的函数,如 _safeHandleRequest 和 _createErrorResponse,这两个函数在我们的处理程序中都用到了。_safeHandleRequest 调用的函数是 try 和 except 块内部的第二个参数。它会捕获很多标准异常,如“未知 URI”和“拒绝访问”,并将这些异常转为适当的 HTTP 相应(在这里各为 404 和 403)。
清单 5:定制的 WSDL 处理程序
from FtServer.Protocols.Http import CommonHandler
from FtServer import Core
class WsdlHandler(CommonHandler.CommonHandler):
def handle(self, request):
return self._safeHandleRequest(self._handle, request)
def _handle(self,request):
#See if the required name header is present
if not request.headers.has_key('name'): return None
name = request.headers['name']
repo = Core.GetRepository()
try:
model = repo.getModel()
#Get all of the statements from the model that define WSDL
#documents with the requested name
stmts = model.complete(None,"http://schema.4suite.org#wsdl",name)
if not stmts:
return self._createErrorResponse(request, 404, uri=name)
#Fetch the document
uri = stmts[0].subject
body = repo.fetchDocument(uri).getContent()
finally:
repo.txRollback()
headers = {'Content-type' : 'text/xml; charset=iso-8859-1',
'Content-length' : len(body),
}
return CommonHandler.Response(200, headers, body)
def Register(properties):
#Register are new handler to accept "WSDL" requests
handler = WsdlHandler()
return [(handler.handle, 'WSDL')]
|
要注意的第二件事是文件尾部的 Register 函数。这个函数由服务器在启动时调用。Register 将接受来自配置文件的属性的字典(dictionary),并返回一组元组(tuple)。每个元组的第一项是可调用对象(在这里是一个方法指针),第二项是 HTTP 请求类型,这种请求类型可以调用该方法。在这里,我们希望所有“WSDL”请求类型都可以调用该方法。
最后,在 _handle 函数的开头,我们将检查头中是否有 name 键。如果没有,那么返回 None。如果返回了 None,那么就是告诉服务器我们没有试图处理请求。接着,服务器将查看是否有另外已注册的处理程序在侦听 WSDL 请求方法。如果有,就调用它。如果没有找到处理请求的处理程序,服务器将返回一个 501 错误。
在配置文件中,您将注意到 POST 请求有三个定义好的处理程序:SoapHandler、PostHandler 和 DeleteHandler。下面是使得三个处理程序能够共同存在的机制。SoapHandler 最先查看消息。如果消息中没有 SOAPAction 头,处理程序就返回 None。然后 PostHandler 查看请求。如果请求中没有 template-xslt 查询参数,那么处理程序将返回 None。最后,DeleteHandler 查看请求。如果其中没有“delete”查询参数,那么处理程序返回 None。道理在于,该链中的处理程序之一最终将处理请求,而错误信息不会出现。
要测试样本处理程序,我们应该开始构建自己的样本应用程序。 在应用程序中,我们将连接到资源库,并获取关于“Software Repository”的 WSDL 描述,然后要求用户输入 WSDL 中定义的接口所需的一组参数,最后发出添加新软件的请求。
清单 6中包含第一个步骤。它将使用 Python 的标准 httplib 来连接到服务器并发出“WSDL”类型的 HTTP 请求,然后获取响应。
清单 6:Example1.py,发出 WSDL 请求。
#Request the WSDL
requestor = httplib.HTTP(SERVER_ADDR, SERVER_PORT)
requestor.putrequest('WSDL', '/')
requestor.putheader('Host', SERVER_ADDR)
requestor.putheader('Content-Type', 'text/plain; charset="iso-8859-1"')
requestor.putheader('name', WEB_SERVICE_NAME)
requestor.endheaders()
requestor.send("")
(status_code, message, reply_headers) = requestor.getreply()
reply_body = requestor.getfile().read()
if status_code != 200:
raise Exception("Expected status code 200, actual %d: %s" % (status_code,message))
|
既然有了软件资源库的 WSDL,我们就需要分析它来决定能做什么。为此,我们将使用 Python wsdllib.py 模块。参考资料部分中可以找到关于下载和安装它的指导。
清单 7展示了示例脚本中从服务器分析 WSDL 的部分。
清单 7:Example1.py,分析 WSDL 响应。
#now that we have gotten the WSDL, parse it in
wsdl = wsdllib.ReadFromString(reply_body)
|
分析了 WSDL 之后,我们将调用 CreateSOAPMessage 函数。 该函数将使用 WSDL 来请求用户向 SOAP 消息输入所需信息,然后构建响应体。有了这些简短的描述和代码中的注释,您就能够理解函数中发生了什么事情。
与上一期专栏类似,我们向服务器发送 SOAP 请求,然后分析和显示响应。
清单 8展示了整个示例程序,而
清单 9展示了完整程序的执行示例。
清单 8:Example1.py,完整的程序
import sys, string, httplib, base64, mimetools
import wsdllib
from Ft.Lib import pDomlette
from xml import xpath
SERVER_ADDR = '127.0.0.1'
SERVER_PORT = 8080
WEB_SERVICE_NAME = "Software Repository"
BODY_TEMPLATE = """<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:s="http://spam.com/softrepo"
SOAP-ENV:encodingStyle="http://spam.com/softrepo/encoding"
>
<SOAP-ENV:Body>
<s:Add>
<s:Title><![CDATA[%(title)s]]></s:Title>
<s:Creator><![CDATA[%(creator)s]]></s:Creator>
<s:Home><![CDATA[%(home)s]]></s:Home>
<s:Version><![CDATA[%(version)s]]></s:Version>
<s:Description><![CDATA[%(description)s]]></s:Description>
</s:Add>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>"""
def _GetInputChoice(title,choices):
print "Please enter a selection for '%s'" % title
while 1:
ctr = 0
names = choices.keys()
names.sort()
for name in names:
print " %d. %s" % (ctr,name)
ctr = ctr + 1
print
resp = raw_input("Selection: ")
error = 0
try:
resp = int(resp)
except:
error = 1
else:
if resp < 0 or resp >= ctr:
error = 1
if error:
print "Invalid Entry, please try again"
else:
break
print
return names[resp]
def CreateSOAPMessage(wsdl):
#Get the service name
serviceName = _GetInputChoice("Service",wsdl.services)
service = wsdl.services[serviceName]
#Get the port name
portName = _GetInputChoice("Service Port",service.ports)
port = service.ports[portName]
#Get the corresponding binding
bindingName = port.binding
if bindingName[:4] == 'tns:':
bindingName = bindingName[4:]
binding = wsdl.bindings[bindingName]
portTypeName = binding.type
if portTypeName[:4] == 'tns:':
portTypeName = portTypeName[4:]
portType = wsdl.portTypes[portTypeName]
#Get the operation name
operationName = _GetInputChoice("Operation",portType.operations)
operation = portType.operations[operationName]
messageName = operation.input.message
if messageName[:4] == 'tns:':
messageName = messageName[4:]
message = wsdl.messages[messageName]
#Finally, ask the user for the data about the parts
actualParts = {}
for name,part in message.parts.items():
actualParts[name] = raw_input("Please enter value for '%s': "%name)
body = BODY_TEMPLATE % actualParts
return body
def main():
#Request the WSDL
requestor = httplib.HTTP(SERVER_ADDR, SERVER_PORT)
requestor.putrequest('WSDL', '/')
requestor.putheader('Host', SERVER_ADDR)
requestor.putheader('Content-Type', 'text/plain; charset="iso-8859-1"')
requestor.putheader('name', WEB_SERVICE_NAME)
requestor.endheaders()
requestor.send("")
(status_code, message, reply_headers) = requestor.getreply()
reply_body = requestor.getfile().read()
if status_code != 200:
raise Exception("Expected status code 200, actual %d: %s" % (status_code,message))
#now that we have gotten the WSDL, parse it in
wsdl = wsdllib.ReadFromString(reply_body)
request_body = CreateSOAPMessage(wsdl)
#Now make the SOAP request
blen = len(request_body)
requestor = httplib.HTTP(SERVER_ADDR, SERVER_PORT)
requestor.putrequest('POST', '/softrepo/soap-handler')
requestor.putheader('Host', SERVER_ADDR)
requestor.putheader('Content-Type', 'text/plain; charset="iso-8859-1"')
requestor.putheader('Content-Length', str(blen))
requestor.putheader('SOAPAction', "")
requestor.endheaders()
requestor.send(request_body)
(status_code, message, reply_headers) = requestor.getreply()
reply_body = requestor.getfile().read()
if status_code != 200:
raise Exception("Expected status code 200, actual %d: %s" % (status_code,message))
reader = pDomlette.PyExpatReader()
dom = reader.fromString(reply_body)
con = xpath.Context.Context(dom,processorNss = {'ftsoap':'http://spam.com/softrepo'})
doc = xpath.Evaluate("//ftsoap:Document",context=con)[0]
uri = doc.getAttributeNS('','uri')
print "New Document (uri = %s)" % uri
print base64.decodestring(doc.firstChild.data)
if __name__ == '__main__':
main()
|
清单 9:Example1.py,执行样本
[molson@penny code]$ python src/example1.py
Please enter a selection for 'Service'
0. SoftwareRepoService
Selection: 0
Please enter a selection for 'Service Port'
0. SoftwareRepoPort
Selection: 0
Please enter a selection for 'Operation'
0. AddSoftware
Selection: 0
Please enter value for 'title': New Software
Please enter value for 'description': This is a new software
Please enter value for 'version': 0.001
Please enter value for 'home': http://new-software.com
Please enter value for 'creator': Mike.Olson@fourthought.com
New Document (uri = /softrepo/incoming/New Software-0.001.xml)
<rdf:RDF
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:dc="http://purl.org/dc/elements/1.1"
xmlns="http://namespaces.4suite.org/www/software-map"
>
<Software rdf:ID="New Software">
<dc:Title>New Software</dc:Title>
<dc:Creator>Mike.Olson@fourthought.com</dc:Creator>
<Home rdf:resource="http://new-software.com"/>
<CurrentVersion>0.001</CurrentVersion>
<dc:Description>This is a new software</dc:Description>
</Software>
</rdf:RDF>
[molson@penny code]$
|

 |

|
总结我们的软件资源库
我们现在知道了如何使用 4Suite Server 来实现 Web 服务。在第一期专栏中,我们构建了一个软件资源库,允许您使用 HTTP 从资源库查看和检索软件包。在第二期专栏中,我们扩展了这个示例,允许 HTTP POST 在软件资源库中创建新的条目。我们还展示了如何使用 SOAP 向资源库添加包。最后,我们在本专栏中创建了定义软件资源库服务的 WSDL 文档,然后连接到资源库,并使用 WSDL 创建 SOAP 消息向资源库添加新的包。
在本专栏的下一部分中,我们将看看在 Python 中处理 SOAP 消息的一些其它做法。
参考资料
关于作者  | |  | Mike Olson 与 Uche Ogbuji 是 Fourthought Inc. 的合伙创始人和首席顾问,他们在这里开发了开放源代码工具
4Suite和
4Suite Server,并为使用 XML 和 Web 服务的客户提供商业咨询、培训和 4Suite 定制。他们住在科罗拉多州的博耳德。
|
对本文的评价
|  |