SOAP 和 WSDL
我在本系列文章的 第 1 部分介绍了 WSDL。WSDL 描述了 Web 服务的接口。Web 服务所有者将用 SOAP 来实现他们的接口。因此, WSDL 服务实际上作为 SOAP 服务一样存在。一旦 Web 服务用户拥有 WSDL 文件,他或者她就知晓接口的细节。他或者她就会用 SOAP 来与 Web 服务通信。
可以把 Web 服务考虑为对象,可以通过 WSDL 接口公开并且使用 SOAP 通过因特网远程访问。既然服务是对象,那么肯定有每种服务的相关属性和每种服务调用的行为。SOAP 消息是 XML 文档,可通过 HTTP 工作。
为什么用 SOAP?[color=darkred][/color]
B2B(Business-to-business)和 A2A(application-to-application )需求表明企业之间为交换信息而相互通信。这种概念被用在 B2B、工作流和跨企业集成中。例如,设想一条垂直供应链,在链上一家企业为了满足它的客户需求而需要调用其提供者的服务。而一些提供者需要沿供应链进一步下行来调用其它企业的服务。
很明显,在此应用程序中互操作性是最为重要的。任何单个企业只能实现 SOAP 通信通道的一端。另一端将是因特网上 任何地方的实体。
在最近几年里,企业之间的集成和互操作性已经成为软件工程师和企业的一个挑战性任务。平台相关性也成为取得集成和互操作性的一个大问题。SOAP 依然是在企业间取得集成和互操作性最简单的机制。
SOAP 体系结构
有了对 SOAP 和它的用途的基本理解,我现在就展开对其体系结构的讨论以了解一些深层知识。请参阅 图 1, 在此图里面您可以识别典型 SOAP 通信体系结构中的一些组件:
SOAP 客户机
SOAP 服务器
实际服务
图 1. 一个典型 SOAP 通信体系结构的组件
让我解释上面所提到的每个实体的体系结构角色。下面的讨论参照 图 1。
SOAP 客户机
SOAP 客户机是一台有 SOAP 机制的机器,它可以产生 SOAP 请求并通过 HTTP 发送到服务器。一条 SOAP 请求是一种类型的 SOAP 消息,通常只有两种类型的 SOAP 消息:一条 SOAP 请求就是一台 SOAP 客户机发送给 SOAP 服务器的内容,一条 SOAP 响应就是 SOAP 服务器对 SOAP 客户机响应的内容。 清单 1是典型的 SOAP 请求,请参阅 清单 2来回顾 SOAP 响应。
清单 1:一条简单的 SOAP 请求
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" >
<SOAP-ENV:Body>
<m:getListOfModels xmlns:m = "uri reference" >
</m:getListOfModels>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
SOAP 服务器
SOAP 服务器也是一台有 SOAP 机制的机器,能够接收来自 SOAP 客户机的请求,并对之作出适当的响应。这些编过码的响应会返回发出请求的 SOAP 客户机。在 SOAP 服务器内部有三个实体:
服务管理器
被部署服务的列表
XML 转换程序
服务管理器负责根据请求管理服务。请参阅 清单 1 的服务请求,在这里元素 <m:getListOfModels xmlns:m="urn:MobilePhoneservice" > 包含了服务的名称。服务管理器会读取 SOAP 客户机想调用的 SOAP 服务的名称并检查所需的服务实际上是否驻留于这台 SOAP 服务器上。此后,它会查询被部署服务的列表(SOAP 服务器所托管的所有服务的列表)。若存在,服务管理器将把 SOAP 请求传送给 XML 转换程序。XML 转换程序就负责将 SOAP 请求的 XML 结构转换成程序员用来实现实际服务的编程语言(例如,Java 编程语言)的结构。还要负责将来自实际服务的响应转换回 SOAP 响应的 XML 结构。请参阅 清单 2获得 SOAP 响应的说明。
清单 2:一条简单的 SOAP 响应
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<m:getListOfModelsResponse xmlns:m="urn:MobilePhoneservice">
<Model>M1</Model>
<Model>M2</Model>
<Model>M3</Model>
</m:getPriceResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
实际服务
图 1中标有 actual service的框就是实际服务驻留的位置。服务实现可以是:例如,COM 组件或 JavaBeans 组件的形式。XML 转换程序负责将 XML 结构转换成合适的方法调用。当 XML 转换程序调用了实际服务实现的某个方法时,这个方法就会完成它的工作并且将结果信息返回 XML 转换程序。
请看一看 图 1中连接 XML translator 和 actual service 的箭头。箭头的两端同在一个企业内,这意味着同一个组织控制着通信两端的接口。与穿过企业边界的在 SOAP 客户机和 SOAP 服务器之间的箭头相比,这正是 SOAP 的目的所在。
SOAP 请求响应机制
当 SOAP 客户机向 SOAP 服务器发送 SOAP 消息时,用 HTTP 协议传输。这就叫做 SOAP 与 HTTP 绑定。当 SOAP 服务器收到消息时,将消息交给服务管理器。服务管理器检查被部署服务的列表,查找在 SOAP 消息中所需的服务。若没有查找到所请求的服务,它将请求失败返回给 SOAP 客户机。但是若此项服务可以提供,控制权由服务管理器转移给 XML 转换程序(转换程序完成合适语言的转换并访问实际服务实现)。服务实现会处理请求并将结果返回给 XML 转换程序。XML 转换程序将结果转换成 SOAP 客户机能够理解的 SOAP 响应(XML 文档)。然后又一次用 HTTP 绑定来传输 SOAP 响应。现在让我们看一下 SOAP 与 HTTP 的绑定细节。
SOAP 与 HTTP 绑定
当您将 SOAP 和 HTTP 绑定在一起或在 HTTP 上操作 SOAP 时,您实际上将 HTTP 报头加到了 SOAP 请求和响应上了。 清单 1是典型 SOAP 请求的结构,而清单 3、 4、 5和 6都是完整的 HTTP 请求,用来演示如何将 HTTP 报头添加到 清单 1上。相似地, 清单 7是一条完整的 HTTP 响应,针对于来自 清单 2的 SOAP 响应。
无论您何时在 HTTP 上使用 SOAP,Content-Type 字段必须是 text/xml。现在您可以察看 清单 3到 清单 7的详情。
使用 HTTP 的 SOAP 请求
您可以将 SOAP 和 HTTP 的 POST请求方法连用。为了发送一条 SOAP HTTP 请求,您需要在 HTTP 中提供一个 SOAPAction 报头字段。 SOAPAction 指定了 SOAP 请求的目的。服务器(例如过滤 HTTP 中 SOAP 请求消息的防火墙)可以用字段 SOAPAction 的值来做决定。
HTTP 客户机在发送一条 SOAP HTTP 请求时必须用此报头字段。SOAPAction 可以有如下几种值:SOAPAction:"URI-Reference"
SOAPAction:"filename"
SOAPAction:""
SOAPAction:
清单 3:演示 SOAPAction 报头字段中的 URI 引用
POST /Vendors HTTP/1.1
Host: www.mobilephoneservice.com
Content-Type:"text/xml";Charset="utf-8"
Content-Length: nnnn
SOAPACtion:"www.mobilephoneservice.com/Vendors/MobilePhoneservice#getListOfModels"
<?xml version="1.0"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" >
<SOAP-ENV:Body>
<m:getListOfModels xmlns:m="urn:MobilePhoneservice" >
</m:getListOfModels>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
清单 3 在 SOAPAction 中包括如下 URI 引用: www.mobilephoneservice.com/Vendors/MobilePhoneservice#getListOfModels
这个 SOAPAction 展示了两部分内容。第一部分是一个特别 SOAP 部署的地址: www.mobilephoneservice.com/Vendors/MobilePhoneservice
第二部分是一个片段标识符,它给出了我们感兴趣的方法的名字(#getListOfModels)。
清单 4:演示 SOAPAction 报头字段中的一个文件名
POST /Vendors HTTP/1.1
Host: www.mobilephoneservice.com
Content-Type:"text/xml";Charset="utf-8"
Content-Length: nnnn
SOAPAction:"MobilePhoneservice#getListOfModels"
<?xml version="1.0"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" >
<SOAP-ENV:Body>
<m:getListOfModels xmlns:m="urn:MobilePhoneservice" >
</m:getListOfModels>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
清单 4 在 SOAPAction 中包含一个文件名( MobilePhoneservice#getListOfModels )。 MobilePhoneservice 文件必须出现在主机 URI( www.mobilephoneservice.com/Vendors )中。 这个主机 URI 是在 HTTP 报头中 host 字段( www.mobilephoneservice.com )和文件夹名( /Vendors )的结合。
清单 5:演示 SOAPAction 报头中的空字符串
POST /Vendors HTTP/1.1
Host: www.mobilephoneservice.com
Content-Type:"text/xml";Charset="utf-8"
Content-Length: nnnn
SOAPAction:""
<?xml version="1.0"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" >
<SOAP-ENV:Body>
<m:getListOfModels xmlns:m="urn:MobilePhoneservice" >
</m:getListOfModels>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
清单 5 在 SOAPAction 中包含一个空字符串("")。空字符串值表明 SOAP 的目的和 Host URI( www.mobilephoneservice.com/Vendors )的目的是一样的。
清单 6:演示无值 SOAPAction 报头
POST /Vendors HTTP/1.1
Host: www.mobilephoneservice.com
Content-Type:"text/xml";Charset="utf-8"
Content-Length: nnnn
SOAPAction:
<?xml version="1.0"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" >
<SOAP-ENV:Body>
<m:getListOfModels xmlns:m ="urn:MobilePhoneservice" >
</m:getListOfModels>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
清单 6没有包含 SOAPAction 值。这表明没有关于消息目的的信息。
用 HTTP 的 SOAP 响应
响应将可能是两种类型的 SOAP 响应中的一种:
一个成功的 SOAP 操作产生 SOAP 结果
一个不成功的 SOAP 操作产生一条 SOAP 错误消息
清单 7:一条带有 HTTP 报头的成功 SOAP 响应
HTTP/1.1 Content-Type:"text/xml"; Charset="utf-8"
Content-Length: nnnn
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" >
<SOAP-ENV:Body>
<m:getListOfModelsResponse xmlns:m = "URI-Reference">
<model>m1</model>
<model>m2</model>
</m:getListOfModels>
</SOAP-ENV:Body>
清单 7是第一种情况,在此可以从 SOAP 服务器取得有意义的结果。
清单 8是一条典型的 SOAP 错误消息。SOAP HTTP 响应遵循 HTTP 中通信状态信息的 HTTP 状态码的语义。若在处理一条请求时发生一个 SOAP 错误,SOAP HTTP 服务器必须发出一条 HTTP 500 "Internal Server Error" 响应,同时在响应中包括一条带有 SOAP 出错元素的 SOAP 消息。
清单 8:一条带有 HTTP 报头的典型 SOAP 错误消息
HTTP/1.1 500 Internal Server Error
Content-Type: "text/xml"; Charset="utf-8"
Content-Length: nnnn
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" >
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<faultstring>Failed to process the request</faultstring>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
使用电子邮件的 SOAP
HTTP 不是唯一绑定 SOAP 消息的解决方案。若 HTTP 不合适,您可以用诸如 SMTP 的其它机制来用于 SOAP 绑定。将 SOAP 和 SMTP 绑定,您可以建立一条单向传输路由。两条单向消息可以用来建立请求/响应通信。 用 SMTP 来发送一条 SOAP 消息,您需要遵从以下步骤:
使用 MIME-Version 报头字段
MIME-Version用一个版本号来区别不同的 MIME 版本。它应用邮件处理代理(例如一个 POP 服务器)来区别旧版本和新版本所生成的邮件消息。请参阅 清单 9,它使用了一个 MIME-Version 报头字段。
清单 9:一个使用电子邮件的 SOAP 示例
TO: <info@waxsys.com>
From: <abc@punjab.com>
Reply-To: <abc@punjab.com>
Date: SAT, 2 Feb 2002 16:00:00
Message-Id: <4FAB345C8D93E93B7A6E9@punjab.com> MIME-Version: 1.0
Content-Type: text/xml; charset=utf-8
Content-Transfer-Encoding: QUOTED-PRINTABLE
<?xml version ="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<prnt:echoString xmlns:prnt="http://waxsys.com">
<msgString>Put your mail Message</msgString>
</prnt:echoString>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
使用 Content-Type 报头字段:
Content-Type用来标识消息主体中的数据类型。对于 SOAP 消息 Content-Type 应该有一个值“text/xml”。请参阅 清单 9,它使用了 Content-Type。
使用 Content-Transfer-Encoding 字段:
Content-Transfer-Encoding 用来指定传输编码的类型,也就是您所要传输的数据是字符格式还是二进制格式。 清单 9使用 Quoted-Printable 编码,这种编码符合依照 ASCII 字符集的可打印字符。这种对数据的编码方式使邮件传输代理不可能修改结果八位元。请参阅 清单 9,它使用了 Content-Transfer-Encoding 。
SOAP 模式与实现
SOAP 消息
一条 SOAP 消息只是一个 XML 文档,由一个强制性的 SOAP Envelope 组成,SOAP Envelope 有一个可选的 SOAP Header 和一个必须有的 SOAP Body。
SOAP 模式的元素:
Envelope
Header
Body
Fault
Envelope:
Envelope 是表示一条 SOAP 消息的顶层元素。为了发送一条 SOAP 消息,必须包括此元素。Envelope 使用必要的 SOAP 名称空间标识符( http://schemas.xmlsoap.org/soap/envelope/ )。若 Envelope 包含了错误的名称空间,会产生一个关于 Envelope 名称空间版本的错误。 清单 10是一个空 Envelope。称其为“空 Envelope”是为了强调在通过“投递”发出它之前,它最终应该包含一封“信”(也许是商业信)。SOAP 模式中的“信”就是指“SOAP Body”,HTTP POST(在 HTTP 与 SOAP 的绑定一部分讨论过)就是传输机制。
清单 10:一个空 SOAP Envelope
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
</SOAP-ENV:Envelope>
Header:
SOAP Header 是可选的。您可以直接将 SOAP Body 放到 SOAP Envelope 中并完全忽略报头。报头提供了一个扩展 SOAP 消息功能的机制。例如,认证就是由 SOAP Header 条目所提供的一种典型扩展。在此情况下,将有一个认证框架,它会使用 SOAP 作为更低级别的传输。请参阅 清单 11来查看在 SOAP 中的报头实现。
清单 11:在一个 SOAP Envelope 中的报头实现
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header>
<m:Order xmlns:m="some URI" SOAP-ENV:mustUnderstand="1">
</m:Order>
</SOAP-ENV:Header>
</SOAP-ENV:Envelope>
Body:
Body 元素包含您实际要发送的消息。它是一个强制性的元素且其子元素通常属于一个用户定义的名称空间。 清单 12 展示了一条引用一个用户定义的名称空间 “u” 的 SOAP 消息。Body 元素是必要信息的容器。这个元素必须在 SOAP 消息中出现并且必须是 SOAP Envelope 元素的一个直接子元素。它也必须直接跟在 SOAP Header 元素的后面。若没有 Header 元素,那么它应直接跟在 Envelope 元素的后面。主体可以包含子元素并且子元素可能是受限于名称空间的。
清单 12: SOAP Envelope 内有 Header,还有 Body
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header>
<m:Order xmlns:m="some URI" SOAP-ENV:mustUnderstand="1">
</m:Order>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<u:GetPrice xmlns:u="some URI" >
<model>m1</model>
</u:GetPrice>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Fault:
这个元素表明一条错误消息。它应作为一个主体条目出现并且不能在 Body 元素中出现一次以上。通常,Fault 元素会在一条 SOAP 响应消息中出现,以表明在 SOAP 请求中出现错误。
Fault 的子元素:
faultcode (错误的标识)
faultstring (错误的描述)
faultactor (标识由谁导致的错误)
detail (错误细节。通常是一个应用程序特定错误,也就是说,它相当于在 SOAP 请求主体中用到地用户定义的名称空间)
清单 13是一条典型的错误消息。
清单 13: 当应用程序出现错误时,SOAP Fault 的使用
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header>
<m:Order xmlns:m="some URI" SOAP-ENV:mustUnderstand="1">
</m:Order>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<faultcode>SOAP-ENV:Server</faultcode>
<faultstring>Not necessary information</faultstring>
<detail>
<d:faultdetail xmlns:d = "uri-referrence">
<msg>
application is not responding properly.
</msg>
<errorcode>12</errorcode>
</d:faultdetail>
</detail>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
来自第 1 部分的一条对 WSDL 文件的 SOAP 请求
已经解释了 SOAP 消息(请求和响应)的常规语法,我将展示如何对本系列 第 1 部分中的 MobilePhoneservice 开发一条 SOAP 请求。在第 1 部分中您设计一个完整的 WSDL 接口来解释 MobilPhoneservice。移动公司在 MobilePhoneservice 中提供了两种方法,一种是 getListOfModels() ,另一种是 getPrice(modelNumber) 。 GetListOfModels() 没有参数但是返回手机型号的一张列表,而 getPrice(modelNumber) 有一个参数 modelNumber 并返回需求型号的 price 。您将用 SOAP 请求格式对它作成文档,但是首先让我展示给您一般的 SOAP 请求和响应格式。
清单 14:SOAP 请求的一般格式
<SOAP-ENV:Envelope xmlns:SOAP-ENV ="SOAP schema's URI"
<SOAP-ENV:Body>
<Instance:"Method Name" xmlns:Instance= "URI where method is located">
<parameter1>value</parameter1>
<parametern>value</parametern>
</Instance:"Method Name">
</SOAP_Envelop:Body>
</SOAP-ENV:Envelope>
一条简单的 SOAP 请求或响应只能表明一种服务的一个方法。包含一条 SOAP 请求的 Envelope 的一般格式遵从 清单 14。将这种一般格式与 清单 16 中的 getListOfModels() 的方法调用请求比较。在清单 16 中,我已经提供了方法和 URI 的名称。既然在 getListOfModels() 中不需要参数,所以 <m:getListOfModels> 在 清单 16中是一个空元素。
清单 15:一条 SOAP 响应的一般格式
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<Instance:"Method Name"+"Response"
xmlns:Instance="URI where method is located">
<return>
<responseparameter1>value</responseparameter1>
<responseparametern>value</responseparametern>
</return>
</Instance: "Method Name"+"Response">
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
清单 15 是一条一般的 SOAP 响应。Apache SOAP 服务器在方法名称的后面增加了"Response"关键字并将返回值封入元素 <return> 中作为一个直接子方法元素。若返回值是复合型结构,那么 <return> 元素包含一个或多个 <item> 元素。将 清单 15与 清单 17 相比,清单 17 是来自 getListOfModels() 的实际响应。 清单 17包含一系列项目,作为 Vector 数据类型,它是返回参数。 相似地, 清单 18和 19 展示了针对 MobilePhoneservice 的方法 getPrice() 的 SOAP 请求和响应。
清单 16:调用 getListOfModels() 方法的 SOAP 请求
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<m:getListOfModels xmlns:m = "www.mobilphoneservice.com" >
</m:getListOfModels>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
清单 17:针对于来自清单 16 请求的 SOAP 响应
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/1999/XMLSchema">
<SOAP-ENV:Body>
<ns1:getListOfModelsResponse xmlns:ns1="urn:MobilePhoneservice"
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<return xmlns:ns2="http://xml.apache.org/xml-soap"
xsi:type="ns2:Vector">
<item xsi:type="xsd:string">M1</item>
<item xsi:type="xsd:string">M2</item>
<item xsi:type="xsd:string">M3</item>
<item xsi:type="xsd:string">M4</item>
<item xsi:type="xsd:string">M5</item>
</return>
</ns1:getListOfModelsResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
清单 18:对于 getPrice 方法的 SOAP 请求
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/1999/XMLSchema">
<SOAP-ENV:Body>
<m:getPrice xmlns:m ="www.mobilphoneservice.com">
<modelNumber xsi:type ="xsd:String">M1</modelNumber>
</m:getPrice>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
清单 19:对于来自清单 18 请求的 SOAP 响应
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/1999/XMLSchema">
<SOAP-ENV:Body>
<ns1:getPriceResponse xmlns:ns1="urn:MobilePhoneservice"
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<return xsi:type="xsd:string"> 5000 </return>
</ns1:getPriceResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
在 SOAP 服务器上部署基于 WSDL 的服务
在此部分您将在 Apache SOAP 服务器上部署来自第 1 部分的 WSDL 服务。Apache SOAP 工具箱将 WSDL 服务信息保存在一个部署描述符文件里面。部署描述符包含了 WSDL 服务的名称和它拥有的所有方法。在运行时部署描述符会将这些名称提供给 SOAP 服务器。同样的部署描述符文件还包含了实现接口的 JavaBean 组件的地址。
清单 20:一个部署描述符的框架
<isd:service xmlns:isd="http://xml.apache.org/xml-soap/deployment"
id="URN:SERVICE-URN">
<isd:provider type="java"
scope="Request"
methods="EXPOSED-METHODS">
<isd:java class="IMPLEMENTING-CLASS"/>
</isd:provider>
<isd:faultListener>org.apache.soap.server.DOMFaultListener
</isd:faultListener>
</isd:service>
清单 20是一个部署描述符的框架,为了作为基于 WSDL 服务的部署描述符使用,它需要三项信息( URN:SERVICE-URN、EXPOSED-METHODS和 IMPLEMENTING-CLASS)。 URN:SERVICE-URN 是被部署服务的名称。在此例中它是 “urn:MobilePhoneservice” 。 EXPOSED-METHODS 是一个单空格分隔的由服务提供的方法的列表。 在此部署中它是 getListOfModels getPrice 。
IMPLEMENTING-CLASS 是带有全路径的 Java 类名称。例如, samples.phonequote.MobilePhoneservice 。 在此例中测试应用程序时,您有如下目录结构:
Apache SOAP 服务器: C:\foo\SOAP-2_2
Mobile phone 服务实现:
C:\foo\SOAP-2_2\samples\phonequote\MobilePhoneservice
因此,IMPLEMENTING-CLASS 路径请参照您安装 SOAP 工具箱的目录。 我没有提供 Java 类的实际实现。 它取决于业务逻辑并且可以是任何东西。
清单 21:MobilePhoneservice 的部署描述符
<isd:service xmlns:isd="http://xml.apache.org/xml-soap/deployment"
id="urn:MobilePhoneservice">
<isd:provider type="java"
scope="Request"
methods="getListOfModels getPrice">
<isd:java class="samples.phonequote.MobilePhoneservice"/>
</isd:provider>
<isd:faultListener>
org.apache.soap.server.DOMFaultListener
</isd:faultListener>
</isd:service>
清单 21是来自第 1 部分对 WSDL 文件的完整部署描述符。
SOAP 客户机与 SOAP 服务器的通信
我已经提供过一个应用程序样本来演示一台 SOAP 客户机与一台 SOAP 服务器的通信。 为此我给过三个列表:Startup.html( 清单 22)、Operation.html( 清单 23)和 Execute.jsp( 清单 24)。
StartUp.html( 清单 22)是一个简单的 HTML 文件,提供给用户一个 GUI 并询问他将要调用哪一个 SOAP 方法。用户会选择一个他需要的方法。
清单 22:一个作为前端的简单 HTML 页
<HTML>
<BODY bgcolor="Teal">
<br/>
<p align="center">
<font size="5" face="Arial" color="white"><b>
SOAP method invocation demo </b></font>
</p>
<hr/>
<font face="Arial" color="whitesmoke" size="3">
<br/><b>
Click any of the method name to execute.<br/>
1. Get the List of all Models that we manufacture....
<a href="execute.jsp?index=1">
<font color="orange"> GetListOfModels </font></a> <br/>
2. Get the Price of any particular model......................
<a href="operation.html">
<font color="orange"> GetPrice </font></a>
</b>
</BODY>
</HTML>
Operation.html( 清单 23)将询问客户提供方法调用所需的参数。
清单 23:根据他或她所选择的方法给予客户一个 GUI
<HTML>
<BODY bgcolor="Teal">
<br/>
<p align="center">
<font size="5" face="Arial" color="white"><b>
GetPrice Operation input Form </b>
</font></p>
<hr/>
<p align="center">
<form action="execute.jsp" method="POST">
<input type="hidden" name="index" value="0">
<table textColor="white">
<tr><td>
<font color="whitesmoke"><b>Description :</b></font>
</td><td><font color="whitesmoke">
Method GetPrice is used to Get Price of given Model Number</font>
</td></tr>
<tr><td>
<font color="whitesmoke"><b>Parameter(s)</b></font></td><td>
</td></tr>
<tr><td><font color="whitesmoke">Model Number </td></font>
<td><font color="whitesmoke">
<input type="text" name="parameter" size="30">
(required) </font>
</td></tr>
<tr><td>
</td><td><input type="Submit" value="Invoke">
</td></tr>
</font>
</table>
</form>
</p>
</BODY>
</HTML>
Execute.jsp( 清单 24)包含了所有的令人感兴趣的代码。它检测所调用的方法和所传递的参数。然后发送给远程服务器一个方法调用。
清单 24:检测方法并发送给远程服务器一个调用
<%@ page language="java" import="java.util.Vector" %>
<%@ page import="java.net.MalformedURLException, java.net.URL" %>
<%@ page import="java.util.Vector" %>
<%@ page import="org.apache.soap.SOAPException,
org.apache.soap.Constants" %>
<%@ page import="org.apache.soap.rpc.Call, org.apache.soap.rpc.Response,
org.apache.soap.rpc.Parameter" %>
<%@ page import="org.apache.soap.transport.http.SOAPHTTPConnection" %>
<%@ page import="org.apache.soap.Fault" %>
<HTML>
<BODY bgcolor="Teal">
<br/>
<p align="center">
<font color="whitesmoke">
<%
boolean isParameter = false ;
SOAPHTTPConnection soapTransport = new SOAPHTTPConnection();
// Address of the remote server.
// Normally this should be dynamically passed and detected.
// We have hard coded it only for demonstration.
URL url = new URL ("http://localhost:8080/soap/servlet/rpcrouter");
// Build the call.
Call call = new Call ();
call.setTargetObjectURI ("urn:MobilePhoneservice");
call.setSOAPTransport (soapTransport);
call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC);
// We'll detect which method user selected
// and give a call accordingly.
// We'll pass parameters if present.
if (request.getParameter("parameter")!=null)
isParameter = true;
if (request.getParameter("index").equals("0"))
{
call.setMethodName("getPrice");
Vector params = new Vector();
String message = new String (request.getParameter("parameter"));
params.addElement (new Parameter("message", String.class,
message , null));
call.setParams(params);
}
else
call.setMethodName("getListOfModels");
Response resp = call.invoke ( url, /* actionURI */ "" );
out.println("<p align=left>
<font size=\"4\" face=\"Arial\" color=\"white\">
Response of [ "+call.getMethodName()+" ]
</font><hr/>");
// Check the response.
if (resp.generatedFault ()) {
Fault fault = resp.getFault ();
out.println("<b>Fault is:</b>"+ fault.getFaultCode ()
+" ["+fault.getFaultString ()+"]");
} else {
Parameter result = resp.getReturnValue ();
out.println("<b>Response is: </b>"+ result.getValue ()+"");
}
%>
<font>
</p>
</BODY>
</HTML>
为了运行此应用程序,您需要两台 Apache SOAP 服务器。 一台服务器将用来与用户通信并托管 清单 22、 23和 24。 另一台服务器(也称为远程服务器)就是我们需要部署第 1 部分所讲的基于 WSDL 服务的地方(在前一节描述,“ 在 SOAP 服务器上基于 WSDL 服务的部署”)。 仅仅是为了演示,远程服务器的地址 http://localhost:8080/soap/servlet/rpcrouter 已经硬编码在 Execute.jsp( 清单 24)中。在实际操作中您可以从 WSDL 文件中读取它。
SOAP 中的简单与复合数据类型
在此节中,我将从解释简单与复合数据类型的不同开始。然后展示如何在 SOAP 中对它们编码。
简单类型包括字符串、浮点数、整数、枚举等。 例如一部手机的“name”的数据类型就是 “string” 。 复合类型由简单类型组成但只代表一个实体。例如, “Student” 类型记录可以有不同的属性,如 “studentName” 属于类型 “string” , “studentRollNumber” 属于类型 “int” 但都只代表一个实体 “Student” 。
清单 25 包含了一个名称为 “Mobile” 的复合数据类型。 您会在后面的 SOAP 请求中用到。
清单 25: “Mobile”类型的模式定义结构
1<? xml version="1.0" ?>
2<xsd:schema xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
3 xmlns:xsd="http://www.w3.org/1999/XMLSchema">
4 targetNameSpace= "www.mobilephoneservice.com/phonequote">
5 <xsd:element name ="Mobile">
6 <xsd:complexType>
7 <xsd:element name="modelNumber" type="xsd:int">
8 <xsd:element name="modelName" type="xsd:string">
9 <xsd:element name="modelWeight" type="xsd:int">
10 <xsd:element name="modelSize" type="xsd:int">
11 <xsd:element name="modelColor">
12 <simpleType base="xsd:string">
13 <enumeration value="white" />
14 <enumeration value="blue" />
15 <enumeration value="black" />
16 <enumeration value="red" />
17 <enumeration value="pink" />
18 </simpleType>
19 </xsd:element>
20 </complexType>
21 </xsd:element>
22</xsd:schema>
在 清单 25 中的第 5 行展示了我们的类型名称(Mobile),而第 6 行说明它是复合数据类型。因复合数据类型有属性,所以在第 7 行到第 12 行展示了定义为子元素的 “Mobile” 数据类型的属性。
第 7 行声明的元素展示了 “Mobile” 类型有一个名称为 “modelNumber” 的属性且其类型为 “int” (也就是说, “modelNumber” 只能采用整数值)。 类似的,第 9 行和第 10 行声明的元素具有同样的类型但有不同的属性名称。在第 8 行定义的元素具有名称为 “modelName” 的属性且其类型是 “string” 。
第 11 行的元素因有位于第 12 行的、名称为 “simpleType” 的子元素,所以需要更好的理解。这里您在复合类型 Mobile 中定义了一个简单类型。simpleType 的名称为 “modelColor” 且它的类型是 “enumeration” 。它有一个属性 “base” 具有的值为 "xsd:string" ,这表明简单类型 “modelColor” 具有在 SOAP 模式中定义的类型 “string” 的功能。在第 13 行到第 17 行中的每一个 <enumeration> 标记都具有一个属性: “value” ( "white" "blue" 、 "black" 、 "red" 和 "pink" )。 枚举类型使我们能够从多项选项中选择一个值。
在 SOAP 请求中使用复合数据类型
清单 26 演示了在 SOAP 请求中复合类型的使用。 它展示了一个在 Body 元素中的携带请求的 Envelope,在 Body 元素中,您正调用 “m” 名称空间的 addModel 方法。 清单 26 使用数据类型 “Mobile” ,此数据类型在 清单 25中定义。
AddModel 方法携带一个类型为 “Mobile” 的参数。我们以 “msd” 名称空间引用来引用 “Mobile” 结构。请参阅清单 26 的 <SOAP-ENV:Envelope> 元素中的 "xmlns:msd" 声明。 这是一个在 SOAP 请求中使用用户定义数据类型的示例。
清单 26:实现在清单 25 中定义的“Mobile”结构
1 <SoAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
2 xmlns:xsd="http://www.w3.org/1999/XMLSchema"
3 xmlns:msd="www.mobilephoneservice.com/phonequote">
4 <SOAP-ENV:Body>
5 <m:addModel xmlns:m="www.mobilephoneservice.com">
6 <msd:Mobile>
7 <modelNumber>1</modelNumber>
8 <modelName>mlr97</modelName>
9 <modelWeight>10</modelWeight>
10 <modelSize>4</modelSize>
11 <modelColor>white</modelColor>
12 </msd:Mobile>
13 </m:addModel>
14 </SOAP-ENV:Body>
15<SOAP-ENV:Envelope>
总结
在这一部分中,您已经学习了 SOAP 语法、请求、响应、HTTP 绑定和使用电子邮件的 SOAP 使用。您也了解了服务于 Apache SOAP 客户机的 Apache SOAP 服务器。最后,我简述了用户定义数据类型的主题,它是一个需要我们仔细学习的高级主题。在此系列文章的下一部分,您会学习更多的用户定义数据类型示例。我还会检验 SOAP 的互操作性(也就是说,怎样使来自不同供应商的 SOAP 实现相互协调)。
我在本系列文章的 第 1 部分介绍了 WSDL。WSDL 描述了 Web 服务的接口。Web 服务所有者将用 SOAP 来实现他们的接口。因此, WSDL 服务实际上作为 SOAP 服务一样存在。一旦 Web 服务用户拥有 WSDL 文件,他或者她就知晓接口的细节。他或者她就会用 SOAP 来与 Web 服务通信。
可以把 Web 服务考虑为对象,可以通过 WSDL 接口公开并且使用 SOAP 通过因特网远程访问。既然服务是对象,那么肯定有每种服务的相关属性和每种服务调用的行为。SOAP 消息是 XML 文档,可通过 HTTP 工作。
为什么用 SOAP?[color=darkred][/color]
B2B(Business-to-business)和 A2A(application-to-application )需求表明企业之间为交换信息而相互通信。这种概念被用在 B2B、工作流和跨企业集成中。例如,设想一条垂直供应链,在链上一家企业为了满足它的客户需求而需要调用其提供者的服务。而一些提供者需要沿供应链进一步下行来调用其它企业的服务。
很明显,在此应用程序中互操作性是最为重要的。任何单个企业只能实现 SOAP 通信通道的一端。另一端将是因特网上 任何地方的实体。
在最近几年里,企业之间的集成和互操作性已经成为软件工程师和企业的一个挑战性任务。平台相关性也成为取得集成和互操作性的一个大问题。SOAP 依然是在企业间取得集成和互操作性最简单的机制。
SOAP 体系结构
有了对 SOAP 和它的用途的基本理解,我现在就展开对其体系结构的讨论以了解一些深层知识。请参阅 图 1, 在此图里面您可以识别典型 SOAP 通信体系结构中的一些组件:
SOAP 客户机
SOAP 服务器
实际服务
图 1. 一个典型 SOAP 通信体系结构的组件
让我解释上面所提到的每个实体的体系结构角色。下面的讨论参照 图 1。
SOAP 客户机
SOAP 客户机是一台有 SOAP 机制的机器,它可以产生 SOAP 请求并通过 HTTP 发送到服务器。一条 SOAP 请求是一种类型的 SOAP 消息,通常只有两种类型的 SOAP 消息:一条 SOAP 请求就是一台 SOAP 客户机发送给 SOAP 服务器的内容,一条 SOAP 响应就是 SOAP 服务器对 SOAP 客户机响应的内容。 清单 1是典型的 SOAP 请求,请参阅 清单 2来回顾 SOAP 响应。
清单 1:一条简单的 SOAP 请求
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" >
<SOAP-ENV:Body>
<m:getListOfModels xmlns:m = "uri reference" >
</m:getListOfModels>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
SOAP 服务器
SOAP 服务器也是一台有 SOAP 机制的机器,能够接收来自 SOAP 客户机的请求,并对之作出适当的响应。这些编过码的响应会返回发出请求的 SOAP 客户机。在 SOAP 服务器内部有三个实体:
服务管理器
被部署服务的列表
XML 转换程序
服务管理器负责根据请求管理服务。请参阅 清单 1 的服务请求,在这里元素 <m:getListOfModels xmlns:m="urn:MobilePhoneservice" > 包含了服务的名称。服务管理器会读取 SOAP 客户机想调用的 SOAP 服务的名称并检查所需的服务实际上是否驻留于这台 SOAP 服务器上。此后,它会查询被部署服务的列表(SOAP 服务器所托管的所有服务的列表)。若存在,服务管理器将把 SOAP 请求传送给 XML 转换程序。XML 转换程序就负责将 SOAP 请求的 XML 结构转换成程序员用来实现实际服务的编程语言(例如,Java 编程语言)的结构。还要负责将来自实际服务的响应转换回 SOAP 响应的 XML 结构。请参阅 清单 2获得 SOAP 响应的说明。
清单 2:一条简单的 SOAP 响应
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<m:getListOfModelsResponse xmlns:m="urn:MobilePhoneservice">
<Model>M1</Model>
<Model>M2</Model>
<Model>M3</Model>
</m:getPriceResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
实际服务
图 1中标有 actual service的框就是实际服务驻留的位置。服务实现可以是:例如,COM 组件或 JavaBeans 组件的形式。XML 转换程序负责将 XML 结构转换成合适的方法调用。当 XML 转换程序调用了实际服务实现的某个方法时,这个方法就会完成它的工作并且将结果信息返回 XML 转换程序。
请看一看 图 1中连接 XML translator 和 actual service 的箭头。箭头的两端同在一个企业内,这意味着同一个组织控制着通信两端的接口。与穿过企业边界的在 SOAP 客户机和 SOAP 服务器之间的箭头相比,这正是 SOAP 的目的所在。
SOAP 请求响应机制
当 SOAP 客户机向 SOAP 服务器发送 SOAP 消息时,用 HTTP 协议传输。这就叫做 SOAP 与 HTTP 绑定。当 SOAP 服务器收到消息时,将消息交给服务管理器。服务管理器检查被部署服务的列表,查找在 SOAP 消息中所需的服务。若没有查找到所请求的服务,它将请求失败返回给 SOAP 客户机。但是若此项服务可以提供,控制权由服务管理器转移给 XML 转换程序(转换程序完成合适语言的转换并访问实际服务实现)。服务实现会处理请求并将结果返回给 XML 转换程序。XML 转换程序将结果转换成 SOAP 客户机能够理解的 SOAP 响应(XML 文档)。然后又一次用 HTTP 绑定来传输 SOAP 响应。现在让我们看一下 SOAP 与 HTTP 的绑定细节。
SOAP 与 HTTP 绑定
当您将 SOAP 和 HTTP 绑定在一起或在 HTTP 上操作 SOAP 时,您实际上将 HTTP 报头加到了 SOAP 请求和响应上了。 清单 1是典型 SOAP 请求的结构,而清单 3、 4、 5和 6都是完整的 HTTP 请求,用来演示如何将 HTTP 报头添加到 清单 1上。相似地, 清单 7是一条完整的 HTTP 响应,针对于来自 清单 2的 SOAP 响应。
无论您何时在 HTTP 上使用 SOAP,Content-Type 字段必须是 text/xml。现在您可以察看 清单 3到 清单 7的详情。
使用 HTTP 的 SOAP 请求
您可以将 SOAP 和 HTTP 的 POST请求方法连用。为了发送一条 SOAP HTTP 请求,您需要在 HTTP 中提供一个 SOAPAction 报头字段。 SOAPAction 指定了 SOAP 请求的目的。服务器(例如过滤 HTTP 中 SOAP 请求消息的防火墙)可以用字段 SOAPAction 的值来做决定。
HTTP 客户机在发送一条 SOAP HTTP 请求时必须用此报头字段。SOAPAction 可以有如下几种值:SOAPAction:"URI-Reference"
SOAPAction:"filename"
SOAPAction:""
SOAPAction:
清单 3:演示 SOAPAction 报头字段中的 URI 引用
POST /Vendors HTTP/1.1
Host: www.mobilephoneservice.com
Content-Type:"text/xml";Charset="utf-8"
Content-Length: nnnn
SOAPACtion:"www.mobilephoneservice.com/Vendors/MobilePhoneservice#getListOfModels"
<?xml version="1.0"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" >
<SOAP-ENV:Body>
<m:getListOfModels xmlns:m="urn:MobilePhoneservice" >
</m:getListOfModels>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
清单 3 在 SOAPAction 中包括如下 URI 引用: www.mobilephoneservice.com/Vendors/MobilePhoneservice#getListOfModels
这个 SOAPAction 展示了两部分内容。第一部分是一个特别 SOAP 部署的地址: www.mobilephoneservice.com/Vendors/MobilePhoneservice
第二部分是一个片段标识符,它给出了我们感兴趣的方法的名字(#getListOfModels)。
清单 4:演示 SOAPAction 报头字段中的一个文件名
POST /Vendors HTTP/1.1
Host: www.mobilephoneservice.com
Content-Type:"text/xml";Charset="utf-8"
Content-Length: nnnn
SOAPAction:"MobilePhoneservice#getListOfModels"
<?xml version="1.0"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" >
<SOAP-ENV:Body>
<m:getListOfModels xmlns:m="urn:MobilePhoneservice" >
</m:getListOfModels>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
清单 4 在 SOAPAction 中包含一个文件名( MobilePhoneservice#getListOfModels )。 MobilePhoneservice 文件必须出现在主机 URI( www.mobilephoneservice.com/Vendors )中。 这个主机 URI 是在 HTTP 报头中 host 字段( www.mobilephoneservice.com )和文件夹名( /Vendors )的结合。
清单 5:演示 SOAPAction 报头中的空字符串
POST /Vendors HTTP/1.1
Host: www.mobilephoneservice.com
Content-Type:"text/xml";Charset="utf-8"
Content-Length: nnnn
SOAPAction:""
<?xml version="1.0"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" >
<SOAP-ENV:Body>
<m:getListOfModels xmlns:m="urn:MobilePhoneservice" >
</m:getListOfModels>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
清单 5 在 SOAPAction 中包含一个空字符串("")。空字符串值表明 SOAP 的目的和 Host URI( www.mobilephoneservice.com/Vendors )的目的是一样的。
清单 6:演示无值 SOAPAction 报头
POST /Vendors HTTP/1.1
Host: www.mobilephoneservice.com
Content-Type:"text/xml";Charset="utf-8"
Content-Length: nnnn
SOAPAction:
<?xml version="1.0"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" >
<SOAP-ENV:Body>
<m:getListOfModels xmlns:m ="urn:MobilePhoneservice" >
</m:getListOfModels>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
清单 6没有包含 SOAPAction 值。这表明没有关于消息目的的信息。
用 HTTP 的 SOAP 响应
响应将可能是两种类型的 SOAP 响应中的一种:
一个成功的 SOAP 操作产生 SOAP 结果
一个不成功的 SOAP 操作产生一条 SOAP 错误消息
清单 7:一条带有 HTTP 报头的成功 SOAP 响应
HTTP/1.1 Content-Type:"text/xml"; Charset="utf-8"
Content-Length: nnnn
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" >
<SOAP-ENV:Body>
<m:getListOfModelsResponse xmlns:m = "URI-Reference">
<model>m1</model>
<model>m2</model>
</m:getListOfModels>
</SOAP-ENV:Body>
清单 7是第一种情况,在此可以从 SOAP 服务器取得有意义的结果。
清单 8是一条典型的 SOAP 错误消息。SOAP HTTP 响应遵循 HTTP 中通信状态信息的 HTTP 状态码的语义。若在处理一条请求时发生一个 SOAP 错误,SOAP HTTP 服务器必须发出一条 HTTP 500 "Internal Server Error" 响应,同时在响应中包括一条带有 SOAP 出错元素的 SOAP 消息。
清单 8:一条带有 HTTP 报头的典型 SOAP 错误消息
HTTP/1.1 500 Internal Server Error
Content-Type: "text/xml"; Charset="utf-8"
Content-Length: nnnn
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" >
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<faultstring>Failed to process the request</faultstring>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
使用电子邮件的 SOAP
HTTP 不是唯一绑定 SOAP 消息的解决方案。若 HTTP 不合适,您可以用诸如 SMTP 的其它机制来用于 SOAP 绑定。将 SOAP 和 SMTP 绑定,您可以建立一条单向传输路由。两条单向消息可以用来建立请求/响应通信。 用 SMTP 来发送一条 SOAP 消息,您需要遵从以下步骤:
使用 MIME-Version 报头字段
MIME-Version用一个版本号来区别不同的 MIME 版本。它应用邮件处理代理(例如一个 POP 服务器)来区别旧版本和新版本所生成的邮件消息。请参阅 清单 9,它使用了一个 MIME-Version 报头字段。
清单 9:一个使用电子邮件的 SOAP 示例
TO: <info@waxsys.com>
From: <abc@punjab.com>
Reply-To: <abc@punjab.com>
Date: SAT, 2 Feb 2002 16:00:00
Message-Id: <4FAB345C8D93E93B7A6E9@punjab.com> MIME-Version: 1.0
Content-Type: text/xml; charset=utf-8
Content-Transfer-Encoding: QUOTED-PRINTABLE
<?xml version ="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<prnt:echoString xmlns:prnt="http://waxsys.com">
<msgString>Put your mail Message</msgString>
</prnt:echoString>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
使用 Content-Type 报头字段:
Content-Type用来标识消息主体中的数据类型。对于 SOAP 消息 Content-Type 应该有一个值“text/xml”。请参阅 清单 9,它使用了 Content-Type。
使用 Content-Transfer-Encoding 字段:
Content-Transfer-Encoding 用来指定传输编码的类型,也就是您所要传输的数据是字符格式还是二进制格式。 清单 9使用 Quoted-Printable 编码,这种编码符合依照 ASCII 字符集的可打印字符。这种对数据的编码方式使邮件传输代理不可能修改结果八位元。请参阅 清单 9,它使用了 Content-Transfer-Encoding 。
SOAP 模式与实现
SOAP 消息
一条 SOAP 消息只是一个 XML 文档,由一个强制性的 SOAP Envelope 组成,SOAP Envelope 有一个可选的 SOAP Header 和一个必须有的 SOAP Body。
SOAP 模式的元素:
Envelope
Header
Body
Fault
Envelope:
Envelope 是表示一条 SOAP 消息的顶层元素。为了发送一条 SOAP 消息,必须包括此元素。Envelope 使用必要的 SOAP 名称空间标识符( http://schemas.xmlsoap.org/soap/envelope/ )。若 Envelope 包含了错误的名称空间,会产生一个关于 Envelope 名称空间版本的错误。 清单 10是一个空 Envelope。称其为“空 Envelope”是为了强调在通过“投递”发出它之前,它最终应该包含一封“信”(也许是商业信)。SOAP 模式中的“信”就是指“SOAP Body”,HTTP POST(在 HTTP 与 SOAP 的绑定一部分讨论过)就是传输机制。
清单 10:一个空 SOAP Envelope
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
</SOAP-ENV:Envelope>
Header:
SOAP Header 是可选的。您可以直接将 SOAP Body 放到 SOAP Envelope 中并完全忽略报头。报头提供了一个扩展 SOAP 消息功能的机制。例如,认证就是由 SOAP Header 条目所提供的一种典型扩展。在此情况下,将有一个认证框架,它会使用 SOAP 作为更低级别的传输。请参阅 清单 11来查看在 SOAP 中的报头实现。
清单 11:在一个 SOAP Envelope 中的报头实现
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header>
<m:Order xmlns:m="some URI" SOAP-ENV:mustUnderstand="1">
</m:Order>
</SOAP-ENV:Header>
</SOAP-ENV:Envelope>
Body:
Body 元素包含您实际要发送的消息。它是一个强制性的元素且其子元素通常属于一个用户定义的名称空间。 清单 12 展示了一条引用一个用户定义的名称空间 “u” 的 SOAP 消息。Body 元素是必要信息的容器。这个元素必须在 SOAP 消息中出现并且必须是 SOAP Envelope 元素的一个直接子元素。它也必须直接跟在 SOAP Header 元素的后面。若没有 Header 元素,那么它应直接跟在 Envelope 元素的后面。主体可以包含子元素并且子元素可能是受限于名称空间的。
清单 12: SOAP Envelope 内有 Header,还有 Body
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header>
<m:Order xmlns:m="some URI" SOAP-ENV:mustUnderstand="1">
</m:Order>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<u:GetPrice xmlns:u="some URI" >
<model>m1</model>
</u:GetPrice>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Fault:
这个元素表明一条错误消息。它应作为一个主体条目出现并且不能在 Body 元素中出现一次以上。通常,Fault 元素会在一条 SOAP 响应消息中出现,以表明在 SOAP 请求中出现错误。
Fault 的子元素:
faultcode (错误的标识)
faultstring (错误的描述)
faultactor (标识由谁导致的错误)
detail (错误细节。通常是一个应用程序特定错误,也就是说,它相当于在 SOAP 请求主体中用到地用户定义的名称空间)
清单 13是一条典型的错误消息。
清单 13: 当应用程序出现错误时,SOAP Fault 的使用
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header>
<m:Order xmlns:m="some URI" SOAP-ENV:mustUnderstand="1">
</m:Order>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<faultcode>SOAP-ENV:Server</faultcode>
<faultstring>Not necessary information</faultstring>
<detail>
<d:faultdetail xmlns:d = "uri-referrence">
<msg>
application is not responding properly.
</msg>
<errorcode>12</errorcode>
</d:faultdetail>
</detail>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
来自第 1 部分的一条对 WSDL 文件的 SOAP 请求
已经解释了 SOAP 消息(请求和响应)的常规语法,我将展示如何对本系列 第 1 部分中的 MobilePhoneservice 开发一条 SOAP 请求。在第 1 部分中您设计一个完整的 WSDL 接口来解释 MobilPhoneservice。移动公司在 MobilePhoneservice 中提供了两种方法,一种是 getListOfModels() ,另一种是 getPrice(modelNumber) 。 GetListOfModels() 没有参数但是返回手机型号的一张列表,而 getPrice(modelNumber) 有一个参数 modelNumber 并返回需求型号的 price 。您将用 SOAP 请求格式对它作成文档,但是首先让我展示给您一般的 SOAP 请求和响应格式。
清单 14:SOAP 请求的一般格式
<SOAP-ENV:Envelope xmlns:SOAP-ENV ="SOAP schema's URI"
<SOAP-ENV:Body>
<Instance:"Method Name" xmlns:Instance= "URI where method is located">
<parameter1>value</parameter1>
<parametern>value</parametern>
</Instance:"Method Name">
</SOAP_Envelop:Body>
</SOAP-ENV:Envelope>
一条简单的 SOAP 请求或响应只能表明一种服务的一个方法。包含一条 SOAP 请求的 Envelope 的一般格式遵从 清单 14。将这种一般格式与 清单 16 中的 getListOfModels() 的方法调用请求比较。在清单 16 中,我已经提供了方法和 URI 的名称。既然在 getListOfModels() 中不需要参数,所以 <m:getListOfModels> 在 清单 16中是一个空元素。
清单 15:一条 SOAP 响应的一般格式
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<Instance:"Method Name"+"Response"
xmlns:Instance="URI where method is located">
<return>
<responseparameter1>value</responseparameter1>
<responseparametern>value</responseparametern>
</return>
</Instance: "Method Name"+"Response">
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
清单 15 是一条一般的 SOAP 响应。Apache SOAP 服务器在方法名称的后面增加了"Response"关键字并将返回值封入元素 <return> 中作为一个直接子方法元素。若返回值是复合型结构,那么 <return> 元素包含一个或多个 <item> 元素。将 清单 15与 清单 17 相比,清单 17 是来自 getListOfModels() 的实际响应。 清单 17包含一系列项目,作为 Vector 数据类型,它是返回参数。 相似地, 清单 18和 19 展示了针对 MobilePhoneservice 的方法 getPrice() 的 SOAP 请求和响应。
清单 16:调用 getListOfModels() 方法的 SOAP 请求
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<m:getListOfModels xmlns:m = "www.mobilphoneservice.com" >
</m:getListOfModels>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
清单 17:针对于来自清单 16 请求的 SOAP 响应
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/1999/XMLSchema">
<SOAP-ENV:Body>
<ns1:getListOfModelsResponse xmlns:ns1="urn:MobilePhoneservice"
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<return xmlns:ns2="http://xml.apache.org/xml-soap"
xsi:type="ns2:Vector">
<item xsi:type="xsd:string">M1</item>
<item xsi:type="xsd:string">M2</item>
<item xsi:type="xsd:string">M3</item>
<item xsi:type="xsd:string">M4</item>
<item xsi:type="xsd:string">M5</item>
</return>
</ns1:getListOfModelsResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
清单 18:对于 getPrice 方法的 SOAP 请求
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/1999/XMLSchema">
<SOAP-ENV:Body>
<m:getPrice xmlns:m ="www.mobilphoneservice.com">
<modelNumber xsi:type ="xsd:String">M1</modelNumber>
</m:getPrice>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
清单 19:对于来自清单 18 请求的 SOAP 响应
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/1999/XMLSchema">
<SOAP-ENV:Body>
<ns1:getPriceResponse xmlns:ns1="urn:MobilePhoneservice"
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<return xsi:type="xsd:string"> 5000 </return>
</ns1:getPriceResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
在 SOAP 服务器上部署基于 WSDL 的服务
在此部分您将在 Apache SOAP 服务器上部署来自第 1 部分的 WSDL 服务。Apache SOAP 工具箱将 WSDL 服务信息保存在一个部署描述符文件里面。部署描述符包含了 WSDL 服务的名称和它拥有的所有方法。在运行时部署描述符会将这些名称提供给 SOAP 服务器。同样的部署描述符文件还包含了实现接口的 JavaBean 组件的地址。
清单 20:一个部署描述符的框架
<isd:service xmlns:isd="http://xml.apache.org/xml-soap/deployment"
id="URN:SERVICE-URN">
<isd:provider type="java"
scope="Request"
methods="EXPOSED-METHODS">
<isd:java class="IMPLEMENTING-CLASS"/>
</isd:provider>
<isd:faultListener>org.apache.soap.server.DOMFaultListener
</isd:faultListener>
</isd:service>
清单 20是一个部署描述符的框架,为了作为基于 WSDL 服务的部署描述符使用,它需要三项信息( URN:SERVICE-URN、EXPOSED-METHODS和 IMPLEMENTING-CLASS)。 URN:SERVICE-URN 是被部署服务的名称。在此例中它是 “urn:MobilePhoneservice” 。 EXPOSED-METHODS 是一个单空格分隔的由服务提供的方法的列表。 在此部署中它是 getListOfModels getPrice 。
IMPLEMENTING-CLASS 是带有全路径的 Java 类名称。例如, samples.phonequote.MobilePhoneservice 。 在此例中测试应用程序时,您有如下目录结构:
Apache SOAP 服务器: C:\foo\SOAP-2_2
Mobile phone 服务实现:
C:\foo\SOAP-2_2\samples\phonequote\MobilePhoneservice
因此,IMPLEMENTING-CLASS 路径请参照您安装 SOAP 工具箱的目录。 我没有提供 Java 类的实际实现。 它取决于业务逻辑并且可以是任何东西。
清单 21:MobilePhoneservice 的部署描述符
<isd:service xmlns:isd="http://xml.apache.org/xml-soap/deployment"
id="urn:MobilePhoneservice">
<isd:provider type="java"
scope="Request"
methods="getListOfModels getPrice">
<isd:java class="samples.phonequote.MobilePhoneservice"/>
</isd:provider>
<isd:faultListener>
org.apache.soap.server.DOMFaultListener
</isd:faultListener>
</isd:service>
清单 21是来自第 1 部分对 WSDL 文件的完整部署描述符。
SOAP 客户机与 SOAP 服务器的通信
我已经提供过一个应用程序样本来演示一台 SOAP 客户机与一台 SOAP 服务器的通信。 为此我给过三个列表:Startup.html( 清单 22)、Operation.html( 清单 23)和 Execute.jsp( 清单 24)。
StartUp.html( 清单 22)是一个简单的 HTML 文件,提供给用户一个 GUI 并询问他将要调用哪一个 SOAP 方法。用户会选择一个他需要的方法。
清单 22:一个作为前端的简单 HTML 页
<HTML>
<BODY bgcolor="Teal">
<br/>
<p align="center">
<font size="5" face="Arial" color="white"><b>
SOAP method invocation demo </b></font>
</p>
<hr/>
<font face="Arial" color="whitesmoke" size="3">
<br/><b>
Click any of the method name to execute.<br/>
1. Get the List of all Models that we manufacture....
<a href="execute.jsp?index=1">
<font color="orange"> GetListOfModels </font></a> <br/>
2. Get the Price of any particular model......................
<a href="operation.html">
<font color="orange"> GetPrice </font></a>
</b>
</BODY>
</HTML>
Operation.html( 清单 23)将询问客户提供方法调用所需的参数。
清单 23:根据他或她所选择的方法给予客户一个 GUI
<HTML>
<BODY bgcolor="Teal">
<br/>
<p align="center">
<font size="5" face="Arial" color="white"><b>
GetPrice Operation input Form </b>
</font></p>
<hr/>
<p align="center">
<form action="execute.jsp" method="POST">
<input type="hidden" name="index" value="0">
<table textColor="white">
<tr><td>
<font color="whitesmoke"><b>Description :</b></font>
</td><td><font color="whitesmoke">
Method GetPrice is used to Get Price of given Model Number</font>
</td></tr>
<tr><td>
<font color="whitesmoke"><b>Parameter(s)</b></font></td><td>
</td></tr>
<tr><td><font color="whitesmoke">Model Number </td></font>
<td><font color="whitesmoke">
<input type="text" name="parameter" size="30">
(required) </font>
</td></tr>
<tr><td>
</td><td><input type="Submit" value="Invoke">
</td></tr>
</font>
</table>
</form>
</p>
</BODY>
</HTML>
Execute.jsp( 清单 24)包含了所有的令人感兴趣的代码。它检测所调用的方法和所传递的参数。然后发送给远程服务器一个方法调用。
清单 24:检测方法并发送给远程服务器一个调用
<%@ page language="java" import="java.util.Vector" %>
<%@ page import="java.net.MalformedURLException, java.net.URL" %>
<%@ page import="java.util.Vector" %>
<%@ page import="org.apache.soap.SOAPException,
org.apache.soap.Constants" %>
<%@ page import="org.apache.soap.rpc.Call, org.apache.soap.rpc.Response,
org.apache.soap.rpc.Parameter" %>
<%@ page import="org.apache.soap.transport.http.SOAPHTTPConnection" %>
<%@ page import="org.apache.soap.Fault" %>
<HTML>
<BODY bgcolor="Teal">
<br/>
<p align="center">
<font color="whitesmoke">
<%
boolean isParameter = false ;
SOAPHTTPConnection soapTransport = new SOAPHTTPConnection();
// Address of the remote server.
// Normally this should be dynamically passed and detected.
// We have hard coded it only for demonstration.
URL url = new URL ("http://localhost:8080/soap/servlet/rpcrouter");
// Build the call.
Call call = new Call ();
call.setTargetObjectURI ("urn:MobilePhoneservice");
call.setSOAPTransport (soapTransport);
call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC);
// We'll detect which method user selected
// and give a call accordingly.
// We'll pass parameters if present.
if (request.getParameter("parameter")!=null)
isParameter = true;
if (request.getParameter("index").equals("0"))
{
call.setMethodName("getPrice");
Vector params = new Vector();
String message = new String (request.getParameter("parameter"));
params.addElement (new Parameter("message", String.class,
message , null));
call.setParams(params);
}
else
call.setMethodName("getListOfModels");
Response resp = call.invoke ( url, /* actionURI */ "" );
out.println("<p align=left>
<font size=\"4\" face=\"Arial\" color=\"white\">
Response of [ "+call.getMethodName()+" ]
</font><hr/>");
// Check the response.
if (resp.generatedFault ()) {
Fault fault = resp.getFault ();
out.println("<b>Fault is:</b>"+ fault.getFaultCode ()
+" ["+fault.getFaultString ()+"]");
} else {
Parameter result = resp.getReturnValue ();
out.println("<b>Response is: </b>"+ result.getValue ()+"");
}
%>
<font>
</p>
</BODY>
</HTML>
为了运行此应用程序,您需要两台 Apache SOAP 服务器。 一台服务器将用来与用户通信并托管 清单 22、 23和 24。 另一台服务器(也称为远程服务器)就是我们需要部署第 1 部分所讲的基于 WSDL 服务的地方(在前一节描述,“ 在 SOAP 服务器上基于 WSDL 服务的部署”)。 仅仅是为了演示,远程服务器的地址 http://localhost:8080/soap/servlet/rpcrouter 已经硬编码在 Execute.jsp( 清单 24)中。在实际操作中您可以从 WSDL 文件中读取它。
SOAP 中的简单与复合数据类型
在此节中,我将从解释简单与复合数据类型的不同开始。然后展示如何在 SOAP 中对它们编码。
简单类型包括字符串、浮点数、整数、枚举等。 例如一部手机的“name”的数据类型就是 “string” 。 复合类型由简单类型组成但只代表一个实体。例如, “Student” 类型记录可以有不同的属性,如 “studentName” 属于类型 “string” , “studentRollNumber” 属于类型 “int” 但都只代表一个实体 “Student” 。
清单 25 包含了一个名称为 “Mobile” 的复合数据类型。 您会在后面的 SOAP 请求中用到。
清单 25: “Mobile”类型的模式定义结构
1<? xml version="1.0" ?>
2<xsd:schema xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
3 xmlns:xsd="http://www.w3.org/1999/XMLSchema">
4 targetNameSpace= "www.mobilephoneservice.com/phonequote">
5 <xsd:element name ="Mobile">
6 <xsd:complexType>
7 <xsd:element name="modelNumber" type="xsd:int">
8 <xsd:element name="modelName" type="xsd:string">
9 <xsd:element name="modelWeight" type="xsd:int">
10 <xsd:element name="modelSize" type="xsd:int">
11 <xsd:element name="modelColor">
12 <simpleType base="xsd:string">
13 <enumeration value="white" />
14 <enumeration value="blue" />
15 <enumeration value="black" />
16 <enumeration value="red" />
17 <enumeration value="pink" />
18 </simpleType>
19 </xsd:element>
20 </complexType>
21 </xsd:element>
22</xsd:schema>
在 清单 25 中的第 5 行展示了我们的类型名称(Mobile),而第 6 行说明它是复合数据类型。因复合数据类型有属性,所以在第 7 行到第 12 行展示了定义为子元素的 “Mobile” 数据类型的属性。
第 7 行声明的元素展示了 “Mobile” 类型有一个名称为 “modelNumber” 的属性且其类型为 “int” (也就是说, “modelNumber” 只能采用整数值)。 类似的,第 9 行和第 10 行声明的元素具有同样的类型但有不同的属性名称。在第 8 行定义的元素具有名称为 “modelName” 的属性且其类型是 “string” 。
第 11 行的元素因有位于第 12 行的、名称为 “simpleType” 的子元素,所以需要更好的理解。这里您在复合类型 Mobile 中定义了一个简单类型。simpleType 的名称为 “modelColor” 且它的类型是 “enumeration” 。它有一个属性 “base” 具有的值为 "xsd:string" ,这表明简单类型 “modelColor” 具有在 SOAP 模式中定义的类型 “string” 的功能。在第 13 行到第 17 行中的每一个 <enumeration> 标记都具有一个属性: “value” ( "white" "blue" 、 "black" 、 "red" 和 "pink" )。 枚举类型使我们能够从多项选项中选择一个值。
在 SOAP 请求中使用复合数据类型
清单 26 演示了在 SOAP 请求中复合类型的使用。 它展示了一个在 Body 元素中的携带请求的 Envelope,在 Body 元素中,您正调用 “m” 名称空间的 addModel 方法。 清单 26 使用数据类型 “Mobile” ,此数据类型在 清单 25中定义。
AddModel 方法携带一个类型为 “Mobile” 的参数。我们以 “msd” 名称空间引用来引用 “Mobile” 结构。请参阅清单 26 的 <SOAP-ENV:Envelope> 元素中的 "xmlns:msd" 声明。 这是一个在 SOAP 请求中使用用户定义数据类型的示例。
清单 26:实现在清单 25 中定义的“Mobile”结构
1 <SoAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
2 xmlns:xsd="http://www.w3.org/1999/XMLSchema"
3 xmlns:msd="www.mobilephoneservice.com/phonequote">
4 <SOAP-ENV:Body>
5 <m:addModel xmlns:m="www.mobilephoneservice.com">
6 <msd:Mobile>
7 <modelNumber>1</modelNumber>
8 <modelName>mlr97</modelName>
9 <modelWeight>10</modelWeight>
10 <modelSize>4</modelSize>
11 <modelColor>white</modelColor>
12 </msd:Mobile>
13 </m:addModel>
14 </SOAP-ENV:Body>
15<SOAP-ENV:Envelope>
总结
在这一部分中,您已经学习了 SOAP 语法、请求、响应、HTTP 绑定和使用电子邮件的 SOAP 使用。您也了解了服务于 Apache SOAP 客户机的 Apache SOAP 服务器。最后,我简述了用户定义数据类型的主题,它是一个需要我们仔细学习的高级主题。在此系列文章的下一部分,您会学习更多的用户定义数据类型示例。我还会检验 SOAP 的互操作性(也就是说,怎样使来自不同供应商的 SOAP 实现相互协调)。