让我们来看一个示例WSDL文件的结构及工作原理。请注意这只是一个十分简单的WSDL文档实例。我们的目的只是简单展示下最显著的特征,后面的章节有对其详细的讨论。
<?xml version="1.0" encoding="UTF-8" ?>
<definitions name="FooSample" targetNamespace="http://tempuri.org/wsdl/" xmlns:wsdlns="http://tempuri.org/wsdl/" xmlns:typens="http://tempuri.org/xsd" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:stk="http://schemas.microsoft.com/soap-toolkit/wsdl-extension" xmlns="http://schemas.xmlsoap.org/wsdl/">
<types>
<schema targetNamespace="http://tempuri.org/xsd" xmlns="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" elementFormDefault="qualified" >
</schema>
</types>
<message name="Simple.foo">
<part name="arg" type="xsd:int"/>
</message>
<message name="Simple.fooResponse">
<part name="result" type="xsd:int"/>
</message>
<portType name="SimplePortType">
<operation name="foo" parameterOrder="arg" >
<input message="wsdlns:Simple.foo"/>
<output message="wsdlns:Simple.fooResponse"/>
</operation>
</portType>
<binding name="SimpleBinding" type="wsdlns:SimplePortType">
<stk:binding preferredEncoding="UTF-8" />
<soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
<operation name="foo">
<soap:operation soapAction="http://tempuri.org/action/Simple.foo"/>
<input>
<soap:body use="encoded" namespace="http://tempuri.org/message/" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" />
</input>
<output>
<soap:body use="encoded" namespace="http://tempuri.org/message/" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" />
</output>
</operation>
</binding>
<service name="FOOSAMPLEService">
<port name="SimplePort" binding="wsdlns:SimpleBinding">
<soap:address location="http://carlos:8080/FooSample/FooSample.asp"/>
</port>
</service>
</definitions>
下面只是对该WSDL文档的预览:
第一行定义了该文档是一份XML文档。尽管这一行不是必需的,但这一行有助于XML解析器对WSDL文档进行解析。
第二行定义了WSDL文档的根元素:<definitions>并有一些命名空间属性定义在根元素内,就与<types>的子元素<schema>类似。
<types>元素组成了类型段。当没有数据类型要定义时,该段可能被忽略。在示例WSDL中,该段中没有特定于应用程序的数据类型定义,但还是使用了类型段,只是用于定义在该文档中使用的schema命名空间。
<message>元素组成消息段。如果你将一个操作比作函数,那么<message>元素则定义了函数的所有参数。每个<message>的<part>子元素与参数相对应。输入参数在独立的<message>元素中定义,与输出参数隔离,输出参数定义在自己的<message>元素中。输出参数<message>元素的name属性以“Response”结尾,用于与输入参数进行区分。每一个<part>元素都有name和type属性,就像函数的参数具有类型和名称类似。<part>元素的type属性值可以是XSD基本类型,SOAP定义的类型,WSDL定义的类型或类型段定义的类型。
在PortTypes段中可以定义零个,1个或多个<portType>元素。可以不包含该段的原因是该段可以定义在独立的WSDL文档中。示例中只包含了一个<portType>元素。<portType>元素可以通过<operation>子元素定义一个或多个操作。示例只包含一个<operation>元素,名称为“foo”。这个名称就相当于函数名。<operation>元素可以包含一个,两个或三个子元素:<input>,<output>,<fault>,这三个子元素中的message属性值与消息段中的<message>元素相对应。示例中的整个<portType>元素就相当于C语言中int foo(int arg);函数的定义。
在Binding段中可以定义零个,1个或多个<binding>元素。它的功能是指定每个<operation>请求和响应如何通过网络传输。
在Services段中可以定义零个,1个或多个<service>元素。<service>元素包含<port>子元素并与Binding段中的<binding>元素相对应。
命名空间
根元素<definitions>和子元素<schema>都有namespace属性。
每个namespace属性定义了每个应用在文档中namespace的缩写。例如:“xmlns:xsd”为http://www.w3.org/2001/XMLSchema命名空间定义了xsd缩写。定义后就可以在以后的文档中使用缩写来代表完整的命名空间。
什么是命名空间?命名空间的使用是为了避免名字冲突。如果我创建了一个web Service并在WSDL文件中包括了一个name属性为foo的元素。你准备将这个服务与另一个服务一起使用,如果没有命名空间,则另一个服务中就不能在其WSDL文件中包括“foo”名称。每个服务能使用相同的名字只有当这些名字在相同的实例中表示同一个意思。使用不同的命名空间,我自己的服务中“foo”名字可以与另一个服务中相同的名字表示不同的意思。
属性targetNamespace定义了一个命名空间,所有在元素中的名称都属于这个命名空间。在示例文件中,根元素<definitions>的targetNamespace属性定义为http://tempuri.org/wsdl/。即所有定义在该文档中的名称都属于这个命名空间。<schema>元素有自己的targetNamespace属性,值为http://tempuri.org/xsd,所有定义在<schema>元素中的名字都属于这个命名空间而非上层的命名空间。
<schema>元素的下一行中定义了缺省的命名空间(xmlns="http://www.w3.org/2001/XMLSchema"),所有未经限定的名称都属于这个命名空间。
SOAP消息
一种查看WSDL文件的方法:客户端和服务器端使用该文件,该文件决定了网络上传输的数据内容。尽管SOAP使用底层的IP或HTTP协议,但应用程序决定了高层在客户和服务器端所使用的协议。换句话说,给定一个操作叫“echoInt”,执行简单的返回输入的整型操作,参数的数量,每个参数的类型以及参数在网络中如何传输(serialization)组成了应用程序规定的协议。这样的协议可以由多种方式进行指定,但我相信最好的方法是使用WSDL。如果我们通过这种方法重新审视WSDL,则其不仅是“接口契约”,而且还是一个协议定义语言。
WSDL可以定义SOAP消息是否遵循rpc或document格式。一个rpc-style消息,像示例文件中定义的,就像一个带有零个或多个参数的函数调用。一个document-style消息则更平面化且只需要少量的嵌套层次。下面的XML消息是使用SoapClient object in MS SOAP Toolkit 2.0对同一个WSDL文件解析后发送和接收的结果。
从客户端发起一个函数调用“foo(5131953)”:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<m:foo xmlns:m="http://tempuri.org/message/">
<arg>5131953</arg>
</m:foo>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
从服务器端接到响应:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<SOAPSDK1:fooResponse xmlns:SOAPSDK1="http://tempuri.org/message/">
<result>5131953</result>
</SOAPSDK1:fooResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
函数调用的消息及其返回的消息都是有效的XML。一个SOAP消息包括一个<Envelope>元素,该元素包括可选的<Header>元素及最少一个<Body>元素。发送和接收消息在<Envelope>元素都有单个的<Body>元素。rpc功能调用消息体有一个“foo”操作名,同时返回消息体中包括了“fooResponse”元素。foo元素只有一个子元素<arg>,它代表单个参数,在示例WSDL文件中描述。同样fooResponse元素也有一个<result>子元素。
XML Schema在WSDL类型和消息段
WSDL的数据类型是基于“XML Schema:Datatypes”(XSD)也是W3C所推荐的。XSD文档有三个不同的版本(1999, 2000/10, and 2001),将其申明在<definitions>元素的一个nameSpace属性中,指定在特殊WSDL文件中所使用的版本。
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
Prefix | Equivalent Namespace | Description |
---|---|---|
soapenc | http://schemas.xmlsoap.org/soap/encoding | SOAP 1.1 enc |
wsdl | http://schemas.xmlsoap.org/wsdl/soap | WSDL 1.1 |
xsd | http://www.w3.org/2001/XMLSchema | XML Schema |
XSD定义了两类内建类型:基本和继承,具体的参见:http://www.w3.org/TR/2001/PR-xmlschema-2-20010330
复杂类型
XML Schema允许定义复杂类型,在C语言中则用struct来表示。例如,定义下面C struct:
typedef struct {
string firstName;
string lastName;
long ageInYears;
float weightInLbs;
float heightInInches;
} PERSON;
使用XML Schema来表示:
<xsd:complexType name="PERSON">
<xsd:sequence>
<xsd:element name="firstName" type="xsd:string"/>
<xsd:element name="lastName" type="xsd:string"/>
<xsd:element name="ageInYears" type="xsd:int"/>
<xsd:element name="weightInLbs" type="xsd:float"/>
<xsd:element name="heightInInches" type="xsd:float"/>
</xsd:sequence>
</xsd:complexType>
但是<complexType>能够表达C struct所不能表达的功能。除了<sequence>子元素还允许其它的子元素。可以使用<all>子元素来代替<sequence>:
<xsd:complexType name="PERSON">
<xsd:all>
<xsd:element name="firstName" type="xsd:string"/>
<xsd:element name="lastName" type="xsd:string"/>
<xsd:element name="ageInYears" type="xsd:int"/>
<xsd:element name="weightInLbs" type="xsd:float"/>
<xsd:element name="heightInInches" type="xsd:float"/>
</xsd:all>
</xsd:complexType>
当<all>子元素出现时,表示其内部的子元素可以以任意的顺序出现且每个子元素都是可选的。这就不太像C struct了。
<portType>和<operation>元素
一个portType定义了若干个operation。在portType中的operation元素定义了该portType中所有方法调用的语法。每一个operation元素定义了方法名,参数(使用<message>元素)以及每个参数的类型(<message>中定义的<part>元素)。
一个WSDL文档中可以有多个<portType>元素。每组<portType>包含了一组相关的操作。
在<operation>元素中,只能出现一个<input>,一个<output>,一个<fault>元素,每个元素都有name和message属性。
<input>,<output>,<fault>三个元素的name属性有何作用呢?它的作用是用来对相同操作名进行区分(重载)。
<binding>和<operation>元素
Binding段是用来对传输中的协议,序列化和编码进行全面的定义。因此,Types,Message和Port Type段用来处理抽像的数据内容,Binding段则用来定义传输过程中物理细节。
将binding规范从数据及消息中分离表示服务提供者们如果使用相同的业务类型可以将其标准化到一组操作中(portType)。每一个提供者可以根据自己的要求对binding进行定制。这就有助于将抽像的定义(types, message, portType)从WSDL文档中分离出并定义到独立的文档中,所有的业务规则相同的服务提供者使用相同的抽像定义,而对binding进行定制。例如:银行可以定义一组标准的银行操作并定义到一个抽像的WSDL文档中,而各个银行可以对使用协议,序列化和编码进行定制。
从示例中提取的binding段:
<binding name="SimpleBinding" type="wsdlns:SimplePortType">
<stk:binding preferredEncoding="UTF-8" />
<soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
<operation name="foo">
<soap:operation soapAction="http://tempuri.org/action/foo1"/>
<input name="foo1">
<soap:body use="encoded" namespace="http://tempuri.org/message/" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" />
</input>
</operation>
<operation name="foo">
<soap:operation soapAction="http://tempuri.org/action/foo2"/>
<input name="foo2">
<soap:body use="encoded" namespace="http://tempuri.org/message/" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" />
</input>
</operation>
</binding>
<binding>元素有一个name属性(示例中值为“SimpleBinding”),定义这个name是为了在Services段的<port>元素中对其进行引用。type属性则是对<portType>元素的引用。第二行是MSTK2的扩展元素,<stk:binding>,指定了优先使用的编码类型为UTF-8。
<soap:binding>元素指定了消息格式(rpc)和transport方式。transport属性引用一个namespace指定使用HTTP SOAP协议。
有两个<operation>元素具有相同的名字“foo”,通过<input>元素的name属性进行区分,分别为“foo1”和“foo2”。在<operation>元素中的<soap:operation>元素都包括“soapAction”属性,其值为一个URI。这个URI是由SOAP规范进行指定并使用在SOAP消息中。SOAP消息中带有SOAPAction消息头将<soap:operation>元素中的URI作为其值。soapAction属性只有在HTTP binding中才需要。该属性的用法在写该文档时并不十分明确。建议的用法是服务器可以通过这个属性对消息进行路由而不需要对所有的消息进行解析。<soap:operation>还可以有style属性,该属性可以对<soap:binding>中的style属性进行重载。
<operation>元素可以包含<input>,<output>和<fault>元素,这些元素与portTypes段中的相关元素对应。上面的例子中只有<input>元素。这三个元素中都有一个可选的name属性,在本例中,用来对相同名称的操作进行区分。在示例的<input>元素中是<soap:body>元素,该元素指定了SOAP消息体的内容。这个元素有以下三个属性:
Use:这个属性指定了数据是否进行了编码或是字面的意思。字面的意思为SOAP消息体中的数据格式与抽像定义(Types, messages, portTypes段)完全相同。编码表示使用“encodingStyle”属性值决定了编码。
Namespace:每个SOAP消息体都可以有其自己的命名空间来避免名字冲突。在这个属性中指定的URI会完全的使用到返回消息中。
EncodingStyle:为SOAP编码,该属性的URI值为"http://schemas.xmlsoap.org/soap/encoding/"
Document类型的binding
上一节中,<soap:binding>元素中的style属性指定的值为“rpc”,当该属性指定为document时则会改变传输中的数据序列,使用文档传输来代替函数签名。如果使用这种binding方式,则<message>元素中定义了文档格式而非函数签名。作为例子,考虑下面的WSDL片段:
<definitions xmlns:stns="(SchemaTNS)" xmlns:wtns="(WsdlTNS)" targetNamespace="(WsdlTNS)">
<schema targetNamespace="(SchemaTNS)" elementFormDefault="qualified">
<element name="SimpleElement" type="xsd:int"/>
<element name="CompositElement" type="stns:CompositeType"/>
<complexType name="CompositeType">
<all>
<element name='a' type="xsd:int"/>
<element name='b' type="xsd:string"/>
</all>
</complexType>
</schema>
<message...>
<part name='p1' type="stns:CompositeType"/>
<part name='p2' type="xsd:int"/>
<part name='p3' element="stns:SimpleElement"/>
<part name='p4' element="stns:CompositeElement"/>
</message>
...
</definitions>
schema有两个元素:SimpleElement和CompositElement,后一个类型为CompositeType。唯一的<message>元素定义了四个part:p1,类型为CompositeType;p2,类型为int;p3为SimpleElement以及p4为CompositeElement。下面展示了对由style/use确定的四种binding方式的传输格式:
rpc/literal
<operation name="method1" style="rpc" ...>
<input>
<soap:body parts="p1 p2 p3 p4" use="literal" namespace="(MessageNS)"/>
</input>
</operation>
SOAP消息中的格式:
<soapenv:body... xmlns:mns="(MessageNS)" xmlns:stns="(SchemaTNS)">
<mns:method1>
<mns:p1>
<stns:a>123</stns:a>
<stns:b>hello</stns:b>
</mns:p1>
<mns:p2>123</mns:p2>
<mns:p3>
<stns:SimpleElement>123</stns:SimpleElement>
</mns:p3>
<mns:p4>
<stns:CompositeElement>
<stns:a>123</stns:a>
<stns:b>hello</stns:b>
</stns:CompositeElement>
</mns:p4>
</mns:method1>
</soapenv:body>
rpc/encoded
<operation name="method1" style="rpc" ...>
<input>
<soap:body parts="p1 p2" use="encoded" encoding="http://schemas.xmlsoap.org/soap/encoding/" namespace="(MessageNS)"/>
</input>
</operation>
SOAP消息中的格式:
<soapenv:body... xmlns:mns="(MessageNS)">
<mns:method1>
<p1 HREF="#1" TARGET="_self"/>
<p2>123</p2>
</mns:method1>
<mns:CompositeType id="#1">
<a>123</a>
<b>hello</b>
</mns:CompositeType>
</soapenv:body>
document/literal/type
<operation name="method1" style="document" ...>
<input>
<soap:body parts="p1" use="literal">
</input>
</operation>
SOAP消息中的格式:
<soapenv:body... xmlns:stns="(SchemaTNS)">
<stns:a>123</stns:a>
<stns:b>hello</stns:b>
</soapenv:body>
document/literal/element
<operation name="method1" style="document" ...>
<input>
<soap:body parts="p3 p4" use="literal">
</input>
</operation>
SOAP消息中的格式:
<soapenv:body... xmlns:stns="(SchemaTNS)">
<stns:SimpleElement>123</stns:SimpleElement>
<stns:CompositeElement>
<stns:a>123</stns:a>
<stns:b>hello</stns:b>
</stns:CompositeElement>
</soapenv:body>
document/encoded
<operation name="method1" style="document" ...>
<input>
<soap:body parts="p1 p2" use="encoded" encoding="http://schemas.xmlsoap.org/soap/encoding/" namespace="(MessageNS)"/>
</input>
</operation>
SOAP消息中的格式:
<soapenv:body... xmlns:mns="(MessageNS)">
<mns:CompositeType>
<a>123</a>
<b>hello</b>
</mns:CompositeType>
<soapenc:int>123</soapenc:int>
</soapenv:body>
<service>和<port>元素
一个service有一组<port>元素。每个<port>元素与binding通常有一对一的对应关系。如果多个<port>元素与同一个<binding>相联系,则需要指定可选的location。
<service name="FOOService">
<port name="fooSamplePort" binding="fooSampleBinding">
<soap:address location="http://carlos:8080/fooService/foo.asp"/>
</port>
</service>