SOAP Web Service
SOAP
基本概念
服务器运行环境
- HTTP(Web) Server
处理 HTTP 消息,解析 HTML 文件- Application Server
应用运行隔离
处理 JSP 页面(JSP = Java + HTML)- SOAP Engine
处理 SOAP 消息(响应和请求)
为每个服务生成一个用于处理请求的 Service Stub
负责对 SOAP 数据包进行封装和解析- Web Service
可用的远程方法集合
- Web Service
- SOAP Engine
- Application Server
基本概念
SOAP :Simple Object Access Protocol
简单对象访问协议/简单对象存取协议
定位
本质上是协议
回顾 个人定义的协议:一组通过协商达成的交互规则
不是组件模型(不替代对象和组件),不是编程语言(不取代编程语言),不是一个解决所有问题的办法(不取代其他分布式计算技术)
作用
用于在离散的分布式环境中交换结构化信息
在协议的指导下,使用 XML 技术定义了一个可扩展的消息传递框架
该框架 提供了 一个可以通过各种底层协议交换的 消息构造,其中 XML 用于数据编码
支持 XML-RPC
这其实也体现了 SOAP 的定位,本质上是存取协议而不是传输方式本身,传输方式是协议中可以指定的部分
特点
- 无状态
- 单向
消息结构
在协议的指导下,定义了消息传递框架,框架提供了消息构造(消息结构)
- SOAP Envelope(必选)
- SOAP Header(可选)
由路由中间节点进行处理 - SOAP Body(必选)
由消息接收者进行处理- Fault(可选)
- SOAP Header(可选)
SOAP Header
用于扩展消息传递的质量
- 上下文
- 认证
- 事物
- 管理
- 高级语义
由 Header blocks(Header entries)组成
SOAP Body
用于存储由最终接收者解析和相应的消息
由 Body block(Body entries)组成
如何确定消息存放的位置?
了解 Header 和 Body 的定位,就能确定消息存放的位置
- Header 用于扩展消息传递的质量,内容针对消息传递中介可见,辅助消息传递的内容放 Header
- Body 用于存储由最终接收者解析和响应的消息,内容针对最终接收者可见,要传的消息放 Body
SOAP Fault
用于携带错误或状态信息
- 故障码(faultcode)
- VersionMismatch:SOAP envelope 命名空间不正确
- MustUnderstand:接收消息的结点不能处理带 mustUnderstand 的 SOAP Header block
- Client:客户端出错
- Server:服务端出错
- 故障字符串描述(faultstring)
- 故障发生者(faultactor)
- 细节
交换模型
SOAP 定义了一个简单的消息传递框架
用于在初始发送方与最终接收方之间以 XML 信息集的形式传输指定的信息
利用 SOAP 消息交换实现 RPC
实现 RPC 需要哪些信息?
- 目标节点地址(Target Address)
- 过程或方法名称(Method Name)
- 参数和返回值的标识和值(Key-Value of parameters and return)
- 标识
- 参数分离
- 消息交换模式
如何用 SOAP 消息交换实现 RPC ?
Request
<?xml version='1.0'?>
<env:Envelope xmlns:env="http://wwww.w3.org/2003/05/soap-envelope">
<env:Header>
<t:AttributeName xmlns:t="http://thirdparty.example.org/AttributeName"
env:encodingStyle="http://example.com/encoding"
env:mustUnderstand="true">AttributeValue</t:AttributeName>
</env:Header>
<env:Body>
<m:methodName
env:encodingStyle="http://wwww.w3.org/2003/05/soap-encoding"
xmlns:m="http://www.example.org/">
<m:objectName xmlns:m="http://www.example.com/objectName">
<m:AttributeName>AttributeValue</m:AttributeName>
</m:objectName>
</m:methodName>
</env:Body>
</env:Envelope>
Response
<?xml version="1.0"?>
<env:Envlope xmlns:env="http://www.w3.org/2003/05/soap-envelope">
<env:Header>
<t:attributeName xmlns:t="http://thirdparty.example.org/attributeName"
env:encodingStyle="http://example.com/encoding"
env:mustUnderstand="true">attributeValue</t:attributeName>
</env:Header>
<env:Body>
<m:methodName
env:encodingStyle="http://wwww.w3.org/2003/05/soap-encoding"
xmlns:m="http://www.example.org/">
<m:attributeName>attributeValue</m:attributeName>
<m:URL>http://www.example.org/objectName?attributeName=attributeValue</m:URL>
</m:methodName>
</env:Body>
</env:Envlope>
处理模型
描述 SOAP 节点在接收 SOAP 消息时采取的逻辑操作
最初发送者、传递中介和最终接收者都能是 SOAP 节点
逻辑操作
-
一般处理
- 检查 SOAP 消息语法
- 分析 SOAP 特有元素(带 SOAP "env" 命名空间的元素)
-
不修改消息的情况下,将中间节点添加到消息路径
- 更改消息路径
-
不更改处理模型的情况下更改消息
-
处理 Header Block 属性,以指示下一步的动作
- role:标识 Header block 的预期目标所扮演的角色
指定谁来处理当前 Header block,由 URI 标识
缺省时由最终接收者进行处理- none:任何节点都不处理当前 Header Block
- next:由中间节点或最终接收者处理当前 Header Block
接收到 next 的进行处理 - ultimateReceiver:由最终接收者处理当前 Header Block
- mustUnderstand
- true:必须处理当前 Header Block
- false(缺省):可以处理当前 Header Block
- relay:获取或设置一个值,该值指示当前节点不理解 SOAP Header 时是否将该标头中转到下一个 SOAP 节点。
补充:对 Header Block 的处理
- 删除
- 修改
- 插入
- role:标识 Header block 的预期目标所扮演的角色
对象分析
- 消息内容:消息存储的信息
- 消息路径:消息传递的路径
- 处理模型:接收消息时的逻辑操作
编码方式
SOAP 包含一组用于编码数据类型的内置规则,规范采用 XML Schema 定义数据类型
- 标量类型
- 复合类型
- 数组 array
- 结构体 struct
注意区分这里的数据类型和 XML-RPC 中的 6 + 2,二者的复合类型都只有数组和结构体两种,但是 SOAP 的标量类型要比 XML-RPC 中的 6 种要多
规则由 SOAP 工具包直接实现
深入理解 Literal
Literal 语义:字面意义的
这其实给出了一种解释数据类型的角度
实例:如何判断 5 的数据类型是什么?
在一般的编程语言中,5 的类型是 Integer,'5' 的类型是 Char,"5" 的类型是 String,5.0f 的类型是 Float
这是因为提供了用于判断类型的辅助字符
在 XML 中,只通过标签值是无法判断属性类型的,因为标签值不带辅助符号
要解释数据类型,要么在标签属性中指定数据类型,要么不指定数据类型,由接收者自行选择可读类型
SOAP 总结
- SOAP : Simple Object Access Protocol 简单对象存取协议
- 用于消息交换(communication)的协议,服务于应用间的消息交换
- 给出了一种发送消息的格式(format)
- 应用于网络通信
- 平台无关
- 语言无关
- 基于 XML
- 简单且可扩展
- 允许穿越防火墙(firewall)
因为可以 Communicate Over HTTP,HTTP 可以穿越防火墙 - W3C 推荐
WSDL
基本概念
WSDL :Web Services Description Language Web服务描述语言
- 描述服务
- 定位服务 → 调用服务
WSDL 提供了一种描述 Web 服务的规范
通过 WSDL,Web 服务被描述为一组通信端点,端点由以下两部分组成
- 对操作和消息的抽象定义
- 对网络协议的具体绑定和信息编码
为什么要引入 WSDL?
WSDL 本质上是引进了一套对 Web 服务的标准化描述方式
规范描述,有助于服务可发现
规范描述相当于一种标准化,而标准化是自动化的前提,有助于推动服务发现和服务匹配的自动化
规范描述使得第三方验证通信格式成为可能
文档结构
结构元素详述
个人理解
-
definitions:根元素,用于定义服务名称和指定命名空间
命名空间规范不要求文档存在于给定位置。重要的一点是指定一个唯一的值,与定义的所有其他命名空间不同- types:用于说明自定义数据类型
如果服务仅使用 XML Schema 内置的简单类型(例如字符string和整数),则不需要 types- element:用于说明自定义数据类型的名称和类型(自定义类的类名-类)
- complexType:用于进一步说明自定义数据类型的构成
通过 complexType.name 属性与 element 标签对应- sequence:说明构成自定义数据类型的基本属性名称和基本数据类型(类属性名-类属性类型)
- message:用于定义消息结构
操作可能涉及零到三次信息交换:请求、响应、故障,消息定义了单次交换的信息结构- part:定义了消息的参数格式
通过 element 属性与 types 中的 element 标签对应
- part:定义了消息的参数格式
- portType:用于定义内部实现视角下的内部方法
操作的逻辑分组(抽象层次高于 operation)- operation:用于定义内部实现视角下内部方法的输入输出
客户端可用操作,引用涉及的消息- input:定义了输入参数名称及其类型(类型被抽象/封装为 message,由 message 指定)
通过 message 属性与 message 标签对应 - output:定义了输出参数名称及其类型(类型被抽象/封装为 message,由 message 指定)
通过 message 属性与 message 标签对应
- input:定义了输入参数名称及其类型(类型被抽象/封装为 message,由 message 指定)
- operation:用于定义内部实现视角下内部方法的输入输出
分隔线,分隔线以上的内容抽象地描述了内部实现的方法,分隔线以下的内容具体地描述了外部如何调用方法 要用内部实现的视角看待分隔线以上的内容,用外部调用的视角看待分隔线以下的内容-
binding:用于定义外部调用视角下的内部方法
说明 portType 与网络协议之间的关联
通过 type 属性与 portType 对应-
operation:用于定义外部调用视角下内部方法的输入输出
binding 中的 operation 中的 input 和 output 不需要声明 message 属性,只需要说明参数名称,而portType 中的 operation 中的 input 和 output 需要指定 message 属性,这也体现了引入服务抽象层带来的封装效果,外部调用无需关心内部实现,内部则要保证抽象描述的完整性-
<soap:operation soapAction="" style="document" />:指明消息交换风格
-
input:定义了输入参数名称
- <soap:body>:指定数据类型解释权的归属权
-
output:定义了输出参数名称
- <soap:body>:指定数据类型解释权的归属权
-
-
-
service:用于描述外部调用视角下的服务
- documentation:服务内容具体描述
- port:对内访问的地址(内部路径)
- <soap:address location = "URL"/>:对外公布的地址(外部接口)
- types:用于说明自定义数据类型
引入服务抽象层之前 —→
引入服务抽象层之后 —|→
相当于把服务应用程序的内外显式地划分开来,由此带来路径的划分
- 对内访问的路径(类比房屋内部构造)
- 对外公布的地址(类比房屋门牌号)
<!--- WSDL 实例 --->
<!--- 带 namespace 的通常指向具体的内容,不带 namespace 的一般用作标识--->
<wsdl:definitions xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://hit.com/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:ns1="http://schemas.xmlsoap.org/soap/http" name="readerService" targetNamespace="http://hit.com/">
<wsdl:types>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://hit.com/" elementFormDefault="unqualified" targetNamespace="http://hit.com/" version="1.0">
<xs:element name="getReader" type="tns:getReader" />
<xs:element name="getReaderResponse" type="tns:getReaderResponse" />
<xs:element name="getReaders" type="tns:getReaders" />
<xs:element name="getReadersResponse" type="tns:getReadersResponse" />
<xs:complexType name="getReaders">
<xs:sequence />
</xs:complexType>
<xs:complexType name="getReadersResponse">
<xs:sequence>
<xs:element maxOccurs="unbounded" minOccurs="0" name="return" type="tns:reader" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="reader">
<xs:sequence>
<xs:element minOccurs="0" name="name" type="xs:string" />
<xs:element minOccurs="0" name="password" type="xs:string" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="getReader">
<xs:sequence>
<xs:element minOccurs="0" name="name" type="xs:string" />
<xs:element minOccurs="0" name="password" type="xs:string" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="getReaderResponse">
<xs:sequence>
<xs:element minOccurs="0" name="return" type="tns:reader" />
</xs:sequence>
</xs:complexType>
</xs:schema>
</wsdl:types>
<wsdl:message name="getReader">
<wsdl:part element="tns:getReader" name="parameters"></wsdl:part>
</wsdl:message>
<wsdl:message name="getReadersResponse">
<wsdl:part element="tns:getReadersResponse" name="parameters"></wsdl:part>
</wsdl:message>
<wsdl:message name="getReaders">
<wsdl:part element="tns:getReaders" name="parameters"></wsdl:part>
</wsdl:message>
<wsdl:message name="getReaderResponse">
<wsdl:part element="tns:getReaderResponse" name="parameters"></wsdl:part>
</wsdl:message>
<wsdl:portType name="IReaderService">
<wsdl:operation name="getReaders">
<wsdl:input message="tns:getReaders" name="getReaders"></wsdl:input>
<wsdl:output message="tns:getReadersResponse" name="getReadersResponse"></wsdl:output>
</wsdl:operation>
<wsdl:operation name="getReader">
<wsdl:input message="tns:getReader" name="getReader"></wsdl:input>
<wsdl:output message="tns:getReaderResponse" name="getReaderResponse"></wsdl:output>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="readerServiceSoapBinding" type="tns:IReaderService">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" />
<wsdl:operation name="getReaders">
<soap:operation soapAction="" style="document" />
<wsdl:input name="getReaders">
<soap:body use="literal" />
</wsdl:input>
<wsdl:output name="getReadersResponse">
<soap:body use="literal" />
</wsdl:output>
</wsdl:operation>
<wsdl:operation name="getReader">
<soap:operation soapAction="" style="document" />
<wsdl:input name="getReader">
<soap:body use="literal" />
</wsdl:input>
<wsdl:output name="getReaderResponse">
<soap:body use="literal" />
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="readerService">
<wsdl:port binding="tns:readerServiceSoapBinding" name="ReaderServicePort">
<soap:address location="http://localhost:8081/service/reader" />
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
深入理解 WSDL 文档结构
任务:给出 WSDL,写对应的 Java 文件,感受 WSDL 的描述作用
关于描述与本体之间关系的个人思考
描述是对本体的一种结构性替代
这里的结构性不单指物理层面的具体构成,也包括逻辑层面的抽象构成
描述本身是一种抽象,描述与本体是一对多的关系,这带来的后果是替代过程不可逆
描述先于本体,则描述指导本体如何构成
本体先于描述,则本体指导描述如何替代
之前接触过的描述?
class
是对object
的描述- 特征是对物体的描述
苹果(物体):红色水果(特征)
WSDL 文档阅读顺序,从下往上看
- 确定接口名称
service 中的 name → 接口名称 - 确定接口方法名称
binding 中 operation 的 name → 接口中方法名称 - 确定接口方法参数
- 根据 binding 的 type 属性找 portType
portType.name==binding.type - 根据 portType 中 operation 中 input 的 message 属性找 message
message.name==input.message - 根据 message 中 part 的 element 属性找 types 中对应的 element
element.name==part.element - 根据 element 的 name 属性找 complexType
complexType.name==element.name - complexType 中的 sequence → 接口中参数
参数名称 == sequence.name
参数类型 == sequence.type
- 根据 binding 的 type 属性找 portType
- 确定接口方法返回值
- 根据 binding 的 type 属性找 portType
portType.name==binding.type - 根据 portType 中 operation 中 output的 message 属性找 message
message.name==output.message - 根据 message 中 part 的 element 属性找 types 中对应的 element
element.name==part.element - 根据 element 的 name 属性找 complexType
complexType.name==element.name - complexType 中的 sequence → 返回值
返回值类型 == sequence.type
- 根据 binding 的 type 属性找 portType
- 确定对外提供服务的 URL
port 中 address 的 location → 对外提供服务的 URL
WSDL 标签之间的关联关系
回顾标签层级关系
-
definitions
- types
- element
- complexType
- sequence
- message
- part
- portType
- operation
- input
- output
- operation
内外分割线,上主内,下主外- binding
- operation
- input
- output
- operation
- service
- types
关联关系说明
- service → binding
service.port.binding → binding.name - binding → portType
binding.type → portType.name - portType → message
portType.operation.input/output.message → message.name - message→types
message.part.element → types.scheme.element.name
规律总结:下层标签属性对应上层 name 属性
深入理解 operation 与 message
定位
此处 operation 中的 input 和 output 带 message 属性,是 portType 中的 operation
这就要求我们用内部实现的视角看待
operation 的方向性(本质上是 message 的方向性)
按照个人理解,此处 operation 的方向性 与 message 的方向性谁决定谁都可以,就看个人是否认同消息自治
如果不认同消息自治,则将消息看作普通的状态信息集合,不具有能动性,也就不具有方向性,那么 message 的方向性就由 operation 所决定
如果认同消息自治,则将消息看作具有能动性的状态信息集合,此时可以认为 operation 的方向性是由 message 带来的(即 operation 的方向性由 message 的方向性决定)
认同消息自治的好处在于能与下面所说的 message 标签次序决定 operation 类型 的说法互恰
message 是 operation 的操作对象
个人认同消息自治,operation 的方向性由 message 的方向性决定,可以将操作分为以下四类
- One-way:endpoint 接收消息
- Request/response:endpoint 接收消息后发送相关消息
- Notification:endpoint 发送消息
- Solicit/response:endpoint 发送消息后接收相关消息
操作类型由 message 的次序决定(符合 operation 的方向性由 message 的方向性决定的说法,即二者互恰)
-
One-way
<operation> <input message="messageName"/> </operation>
-
Request/response
<operation> <input message="messageName"/> <output message="messageName"/> </operation>
-
Notification:endpoint
<operation> <output message="messageName"/> </operation>
-
Solicit/response
<operation> <output message="messageName"/> <input message="messageName"/> </operation>
深入理解 SOAP binding
在 WSDL:binding 标签可以带 SOAP binding 子标签,通过指定 SOAP binding 可以指定消息传递类型
SOAP 与 WSDL 的关系
简单回顾 SOAP
全称 Simple Object Access Protocol,本质上是协议
协议指导下构建了一套消息交换框架,该框架提供了 一个可以通过各种底层协议交换的 消息构造,其中 XML 用于数据编码
框架中包含了一组用于编码数据类型的内置规则,编码规则有 SOAP 工具包直接实现,因此 SOAP 规范采用 XML Schema 来定义数据类型,通过这组规则预规定了SOAP简单数据类型(不止6 + 2,包括标量类型和复合类型)
WSDL 中 消息交互可以通过 SOAP 框架实现,这就解释了为什么可以在 <wsdl:binding> 中内嵌<soap:binding>和 <soap:body> 的标签
SOAP 框架支持 RPC 和 Document-style 两种信息交换风格,简单对比下二者特点
对比角度 | RPC | Document-style |
---|---|---|
调用形式 | 过程调用(按方法名调用) | 业务文档(按标识调用) |
格式验证 | Method signature(方法签名) | Schema |
信息处理 | 序列化 | 解析与验证 |
耦合程度 | 紧密耦合 | 松散耦合 |
交互对象 | 简单,点对点(不含中介) | 复杂,端对端(可含中介) |
交互过程 | 同步 | 异步 |
通信范围 | 内网 | 互联网 |
交互范围 | 企业内部 | 企业之间 |
使用流程 | 运行时间较短的业务流程 | 长期业务流程 |
运行带宽 | 可靠高带宽 | 不可靠带宽 |
运行环境 | 受信任环境 | 不可信环境 |
信息交换过程中,除了要指定交换风格以外,还要指定数据类型解释权的归属权,常用的组合有四种(一共有 6 = 2 × (1 + 1 × 2)种,后面有说明),具体的差异体现在 WSDL 和 SOAP 消息结构中
-
rpc/encoded:RPC 交换风格,数据类型解释权的归属权由消息发送者所有(即 SOAP 消息指定了数据类型)
<!--- WSDL ---> <message name="myMethod"> <part name="x" type="xsd:int"/> <part name="y" type="xsd:int"/> </message> <portType> <operation> <input message="myMethod"/> </operation> </portType> <!--- SOAP ---> <soap:envelop> <soap:body> <myMethod> <x xsi:type="xsd:int">5</x> <y xsi:type="xsd:float">5.0</y> </myMethod> </soap:body> </soap:envelop>
-
rpc/literal:RPC 交换风格,数据类型解释权的归属权由消息接收者所有(即 SOAP 消息不指定数据类型)
<!--- WSDL ---> <message name="myMethod"> <part name="x" type="xsd:int"/> <part name="y" type="xsd:int"/> </message> <portType> <operation> <input message="myMethod"/> </operation> </portType> <!--- SOAP ---> <soap:envelop> <soap:body> <myMethod> <x>5</x> <y>5.0</y> </myMethod> </soap:body> </soap:envelop>
-
document/literal:Document-style 交换风格,数据类型解释权的归属权由消息接收者所有(即 SOAP 消息不指定数据类型)
<!--- WSDL ---> <types> <schema> <element name="xElem" type="xsd:int"/> <element name="yElem" type="xsd:float"/> </schema> </types> <message name="myMethod"> <part name="x" type="xElem"/> <part name="y" type="yElem"/> </message> <portType> <operation> <input message="myMethod"/> </operation> </portType> <!--- SOAP ---> <soap:envelop> <soap:body> <myMethod> <xElem>5</xElem> <yElem>5.0</yElem> </myMethod> </soap:body> </soap:envelop>
-
document/literal/wrapped:Document-style 交换风格,数据类型解释权的归属权由消息接收者所有(即 SOAP 消息不指定数据类型),将所有参数包装成一个参数
<!--- WSDL ---> <types> <schema> <xs:element name="myMethodReq"> <xs:complexType> <xs:sequence> <xs:element type="xs:int" name="xElem"/> <xs:element type="xs:float" name="yElem"/> </xs:sequence> </xs:complexType> </xs:element> </schema> </types> <message name="myMethodMsg"> <part name="part1" type="myMethodReq"/> </message> <portType> <operation> <input message="myMethodMsg"/> </operation> </portType> <!--- SOAP ---> <soap:envelop> <soap:body> <myMethodReq> <xElem>5</xElem> <yElem>5.0</yElem> </myMethodReq> </soap:body> </soap:envelop>
差别说明
从交换风格角度看,有 RPC 和 Document-style 两种风格
- RPC 交换风格在 message 中直接指定参数类型
- Document-style 交换风格需要在 type 中对参数类型进行包装
Document-style 中有两种参数包装方式,分别是不带 wrapped 和 带 wrapped 的方式
- 不带 wrapped 的只需要对参数进行一层包装
在 WSDL 片段中表现为 message 中可能有多个 part,types 中 无需引入 complexType
在 SOAP 片段中表现为 soap body 可能有多个直接子标签 - 带 wrapped 的除了对参数进行一层包装以外,还要把包装好的参数再集体打包成一个(相当于包两层)
在WSDL片段中表现为 message 只有一个 part,types 中需要引入 complexType(相当于包成了对象)
在 SOAP 片段中表现为 soap body 只有一个直接子标签
从数据类型解释权的归属权的角度看,有 encoded 和 literal 两种
- encoded 表示发送者有数据类型的解释权,soap:body 中需要指定参数类型
- literal 表示接收者有数据类型的解释权,soap:body 中无需指定参数类型
语义说明
encoded:编码
literal:字面上的
wrapped:包装
WSDL 创作风格推荐
- 注重可复用和可维护
- 分 3 部分存 WSDL 文档
- 数据类型定义
- 抽象定义
- 具体服务绑定
- 能 import 就不自己写
本质上也是复用
Web Service 工具包
Client 通过 SOAP 与 服务实现进行交互
实现服务实现框架中的方法可以获得服务实现(应用)
可以从 Web 服务代码中生成 WSDL(即 服务实现应用 可以生成 WSDL)
WSDL 生成服务器实现(框架和应用中共有的)代码 和 生成用于访问 Web 服务的客户端代理代码
WSDL 总结
- WSDL :Web Services Description Language Web服务描述语言
- 描述服务
- 定位服务
- 平台无关
- 语言无关
- 基于 XML
- W3C推荐