為什么使用WSDL?
像Internet協議之類的標準有沒有為權威所利用,或者人們這樣看待它是因為順之所獲的好處遠遠超出了代價?曾經有許多試圖建立的標準都流產了。有時候,那些還沒有普遍使用的標準甚至由法令或政府規定強行推出:Ada語言就是一例。
我相信正是跟隨標準所帶來的好處使它廣泛接受。例如,對于鐵路服務來說,真正重要的是,不同公司所鋪設的鐵路結合到一起,或者是來自好幾個公司的產品協調的工作在一起。幾家大的企業合力建立了SOAP標準。Web Service描述語言(WSDL)向這種Web Service的提供商和用戶推出了方便的協調工作的方法,使我們能更容易的獲得SOAP的種種好處。幾家公司的鐵道并在一起不算什么難事,他們所需遵循的只是兩軌間的標準距離。對Web Service來說,這要復雜得多。我們必須先制定出指定接口的標準格式。
曾經有人說SOAP并不真需要什么接口描述語言。如果SOAP是交流純內容的標準,那就需要一種語言來描述內容。SOAP消息確實帶有某些類型信息,因此SOAP允許動態的決定類型。但不知道一個函數的函數名、參數的個數和各自類型,怎么可能去調用這個函數呢?沒有WSDL,我可以從必備文檔中確定調用語法,或者檢查消息。隨便何種方法,都必須有人參與,這個過程可能會有錯。而使用了WSDL,我就可以通過這種跨平臺和跨語言的方法使Web Service代理的產生自動化。就像COM和CORBA的IDL文件,WSDL文件由客戶和服務器約定。
注意由于WSDL設計成可以綁定除SOAP以外的其他協議,這里我們主要關注WSDL在HTTP上和SOAP的關系。同樣,由于SOAP目前主要用來調用遠程的過程和函數,WSDL支持SOAP傳輸的文檔規范。WSDL 1.1已經作為記錄遞交給W3C(見http://www.w3.org/TR/wsdl.html)
WSDL文檔結構
若要理解XML文檔,將之看作塊狀圖表非常有用。下圖以XML的文檔形式說明了WSDL的結構,它揭示了WSDL文檔五個欄之間的關系。
WSDL文檔可以分為兩部分。頂部分由抽象定義組成,而底部分則由具體描述組成。抽象部分以獨立于平臺和語言的方式定義SOAP消息,它們并不包含任何隨機器或語言而變的元素。這就定義了一系列服務,截然不同的網站都可以實現。隨網站而異的東西如序列化便歸入底部分,因為它包含具體的定義。
l 抽象定義
Types
獨立與機器和語言的類型定義
Messages
包括函數參數(輸入與輸出分開)或文檔描述
PortTypes
引用消息部分中消息定義來描述函數簽名(操作名、輸入參數、輸出參數)
2 具體定義
Bindings
PortTypes部分的每一操作在此綁定實現
Services
確定每一綁定的端口地址
下面的圖中,箭頭連接符代表文檔不同欄之間的關系。點和箭頭代表了引用或使用關系。雙箭頭代表"修改"關系。3-D的箭頭代表了包含關系。這樣,各Messages欄使用Types欄的定義,PortTypes欄使用Messages欄的定義;Bindings欄引用了PortTypes欄,Services欄引用Bindings欄,PortTypes和Bindings欄包含了operation元素,而Services欄包含了port元素。PortTypes欄里的operation元素由Bindings欄里的operation元素進一步修改或描述。
在此背景中,我將使用標準的XML術語來描述WSDL文檔。Element是指XML的元素,而"attribute"指元素的屬性。于是:
<element attribute="attribute-value">contents</element> |
內容也可能由一個或多個元素以遞歸的方式組成。根元素是所有元素之中最高級的元素。子元素總是從屬于另一個元素,父元素。
注意,文檔之中可能只有一個Types欄,或根本沒有。所有其他的欄可以只有零元素、單元素或是多元素。WSDL的列表要求所有的欄以固定的順序出現:import, types, message, portType, binding, service。所有的抽象可以是單獨存在于別的文件中,也可以從主文檔中導入。

圖一:抽象定義和具體定義
|
WSDL文件示例
讓我們來研究一下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> |
以下是該實例文檔的總述:稍后我將詳細討論每一部分的細節。
第一行申明該文檔是XML。盡管這并不是必需的,但它有助于XML解析器決定是否解析WSDL文件或只是報錯。第二行是WSDL文檔的根元素:<definitions>。一些屬性附屬于根元素,就像<schema>子元素對于<types>元素。
<types>元素包含了Types欄。如果沒有需要聲明的數據類型,這欄可以缺省。在WSDL范例中,沒有應用程序特定的types聲明,但我仍然使用了Types欄,只是為了聲明schema namespaces。
<message>元素包含了Messages欄。如果我們把操作看作函數,<message>元素定義了那個函數的參數。<message>元素中的每個<part>子元素都和某個參數相符。輸入參數在<message>元素中定義,與輸出參數相隔離--輸出參數有自己的<message>元素。兼作輸入、輸出的參數在輸入輸出的<message>元素中有它們相應的<part>元素。輸出<message>元素以"Response"結尾,就像以前所用的"fooResponse"。每個<part>元素都有名字和類型屬性,就像函數的參數有參數名和參數類型。
用于交換文檔時,WSDL允許使用<message>元素來描述交換的文檔。
<part>元素的類型可以是XSD基類型,也可以是SOAP定義類型(soapenc)、WSDL定義類型(wsdl)或是Types欄定義的類型。
一個PortTypes欄中,可以有零個、單個或多個<portType>元素。由于抽象PortType定義可以放置在分開的文件中,在某個WSDL文件中沒有<portType>元素是可能的。上面的例子里只是用了一個<portType>元素。而一個<portType>元素可在<operation>元素中定義一個或是多個操作。示例僅使用了一個名為"foo"的<operation>元素。這和某個函數名相同。<operation>元素可以有一個、兩個、三個子元素:<input>, <output> 和<fault>元素。每個<input>和<output>元素中的消息都引用Message欄中的相關的<message>元素。這樣,示例中的整個<portType>元素就和以下的C函數等效:
這個例子足見XML和C相比要冗長的多。(包括<message>元素,XML在示例中共使用了12行代碼來表達相同的單行函數聲明。)
Bindings欄可以有零個、一個或者多個<binding>元素。它的意圖是制定每個<operation>通過網絡調用和回應。Services欄同樣可以有零個、一個、多個<service>元素。它還包含了<port>元素,每個<port>元素引用一個Bindings欄里的<binding>元素。Bindings和Services欄都包含WSDL文檔。
Namespace
<definitions>和子節點<schema>都是namespace屬性:
<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> |
每個namespace屬性都聲明了一個縮略語,用在文檔中。例如"xmlns:xsd"就為 http://www.w3.org/2001/XMLSchema定義了一個縮略語(xsd)。這就允許對該namespace的引用只需簡單的在名字前加上前綴就可以了,如:"xsd:int"中的"xsd"就是合法的類型名。普通范圍規則可運用于縮略前綴。也就是說,前綴所定義的元素只在元素中有效。
Namespace派什么用?namespace的作用是要避免命名沖突。如果我建立一項Web Service,其中的WSDL文件包含一個名為"foo"的元素,而你想要使用我的服務與另一項服務連接作為補充,這樣的話另一項服務的WSDL文件就不能包含名為"foo"的元素。兩個服務器程序只有在它們在兩個事例中表示完全相同的東西時,才可以取相同的名字。如果有了表示區別的namespace,我的網絡服務里的"foo"就可以表示完全不同于另一個網絡服務里"foo"的含義。在你的客戶端里,你只要加以限制就可以引用我的"foo"。
見下例:http://www.infotects.com/fooService#foo 就是完全限制的名字,相當于"carlos:foo",如果我聲明了carlos作為http://www.infotects.com/fooService的快捷方式。請注意namespace中的URL是用來確定它們的唯一性的,同時也便于定位。URL所指向的地方不必是實際存在的網絡地址,也可以使用GUID來代替或補充URL。例如,GUID"335DB901-D44A-11D4-A96E-0080AD76435D"就是一個合法的namespace指派。
targetNamespace屬性聲明了一個namespace,元素中所有的聲明的名字都列于其內。在WSDL示例中,<definitions>的targetNamespace 是http://tempuri.org/wsdl。這意味著所有在WSDL文檔中聲明的名字都屬于這個namespace。<schema>元素有自己的targetNamespace屬性,其值為 http://tempuri.org/xsd ,在<schma>元素中定義的所有名字都屬于這個namespace而不是main的target namespace。
<schema>元素的以下這行聲明了默認的namespace。Schema中所有有效的名字都屬于這個namespace。
SOAP消息
對于使用WSDL的客戶機和服務機來說,研究WSDL文件的一種方法就是決定什么來接受所發送的信息。盡管SOAP使用底層協議,如IP和HTTP等,但應用程序決定了服務器與客戶機之間交互的高級協議。也就是說,進行一項操作,比如"echoint"把輸入的整數送回,參數的數目、每個參數的類型、以及參數如何傳送等因素決定了應用程序特定的協議。有很多方法可以確定此類協議,但我相信最好的方法就是使用WSDL。如果我們用這種視角來看待它,WSDL不只是一種接口協議,而且是一種協議特定的語言。它就是我們超越"固定"協議(IP、HTTP等)所需要的應用程序特定協議。
WSDL可以確定SOAP消息是否遵從RPC或文檔風格。RPC風格的消息(就是示例中所用的)看起來像是函數調用。而文檔風格的消息則更普通,嵌套層次更小。下面的XML消息就是示例WSDL文件解析后的發送/接受效果,解析使用的是MS SOAP Toolkit 2.0(MSTK2)中的SoapClient對象。
從客戶端調用"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消息由幾部分組成,首先是<Envelop>元素,包含一個可選的<Header>元素以及至少一個<body>元素。Rpc函數所調用的消息體有一個根據操作"foo"命名的元素,而回應信息體有一個"fooResponse"元素。Foo元素有一個部分<arg>,就和WSDL中描述的一樣,是單參數的。fooResponse也相應的有一個<result>的部分。注意encodingStyle、envelope和message的namespace和WSDL Bindings欄中的預定義的一致,重復如下:
<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> |
WSDL的Types欄和Messages欄中的XML Schema
WSDL數據類型是基于"XML Schema: Datatypes"(XSD)的,現在已經被W3C推薦。這一文檔共有三個版本(1999,2000/10,2001),因此必須在namespace屬性的<definitions>元素中指明所使用的是哪一個版本。
xmlns:xsd="http://www.w3.org/2001/XMLSchema" |
在本文中,我將只考慮2001版本。WSDL標準的推薦者強烈建議使用2001版。
在本欄和以后各部分,需使用以下簡縮或前綴
前綴 |
代表的Namespace |
描述 |
Soapenc |
http://schemas.xmlsoap.org/soap/encoding |
SOAP 1.1 encoding |
Wsdl |
http://schemas.xmlsoap.org/wsdl/soap |
WSDL 1.1 |
Xsd |
http://www.w3.org/2001/XMLSchema |
XML Schema |
XSD基類型
下表是直接從MSTK2文檔中取出的,列舉了MSTK2所支持的所有XSD基類型。它也告訴在客戶端或服務器端的WSDL讀取程序如何把XSD類型映射到在VB、C++和IDL中相應的類型。
XSD (Soap)類型 |
變量類型 |
VB |
C++ |
IDL |
Comments |
anyURI |
VT_BSTR |
String |
BSTR |
BSTR |
|
base64Binary |
VT_ARRAY | VT_UI1 |
Byte() |
SAFEARRAY |
SAFEARRAY(unsigned char) |
|
Boolean |
VT_BOOL |
Boolean |
VARIANT_BOOL |
VARIANT_BOOL |
|
Byte |
VT_I2 |
Integer |
short |
short |
轉換時驗證范圍有效性 |
Date |
VT_DATE |
Date |
DATE |
DATE |
時間設為 oo:oo:oo |
DateTime |
VT_DATE |
Date |
DATE |
DATE |
|
Double |
VT_R8 |
Double |
double |
double |
|
Duration |
VT_BSTR |
String |
BSTR |
BSTR |
不轉換和生效 |
ENTITIES |
VT_BSTR |
String |
BSTR |
BSTR |
不轉換和生效 |
ENTITY |
VT_BSTR |
String |
BSTR |
BSTR |
不轉換和生效 |
Float |
VT_R4 |
Single |
float |
float |
|
GDay |
VT_BSTR |
String |
BSTR |
BSTR |
不轉換和生效 |
GMonth |
VT_BSTR |
String |
BSTR |
BSTR |
不轉換和生效 |
GMonthDay |
VT_BSTR |
String |
BSTR |
BSTR |
不轉換和生效 |
GYear |
VT_BSTR |
String |
BSTR |
BSTR |
不轉換和生效 |
GYearMonth |
VT_BSTR |
String |
BSTR |
BSTR |
不轉換和生效 |
ID |
VT_BSTR |
String |
BSTR |
BSTR |
不轉換和生效 |
IDREF |
VT_BSTR |
String |
BSTR |
BSTR |
不轉換和生效 |
IDREFS |
VT_BSTR |
String |
BSTR |
BSTR |
不轉換和生效 |
Int |
VT_I4 |
Long |
long |
long |
|
Integer |
VT_DECIMAL |
Variant |
DECIMAL |
DECIMAL |
轉換時范圍生效 |
Language |
VT_BSTR |
String |
BSTR |
BSTR |
不轉換和生效 |
Long |
VT_DECIMAL |
Variant |
DECIMAL |
DECIMAL |
轉換時范圍生效 |
Name |
VT_BSTR |
String |
BSTR |
BSTR |
不轉換和生效 |
NCName |
VT_BSTR |
String |
BSTR |
BSTR |
不轉換和生效 |
negativeInteger |
VT_DECIMAL |
Variant |
DECIMAL |
DECIMAL |
轉換時范圍生效 |
NMTOKEN |
VT_BSTR |
String |
BSTR |
BSTR |
不轉換和生效 |
NMTOKENS |
VT_BSTR |
String |
BSTR |
BSTR |
不轉換和生效 |
nonNegativeIntege |
VT_DECIMAL |
Variant |
DECIMAL |
DECIMAL |
轉換時范圍生效 |
nonPositiveInteger |
VT_DECIMAL |
Variant |
DECIMA |
DECIMAL |
轉換時范圍生效 |
normalizedString |
VT_BSTR |
String |
BSTR |
BSTR |
|
NOTATION |
VT_BSTR |
String |
BSTR |
BSTR |
不轉換和生效 |
Number |
VT_DECIMAL |
Variant |
DECIMAL |
DECIMAL |
|
positiveInteger |
VT_DECIMAL |
Variant |
DECIMAL |
DECIMAL |
轉換時范圍生效 |
Qname |
VT_BSTR |
String |
BSTR |
BSTR |
不轉換和生效 |
Short |
VT_I2 |
Integer |
short |
short |
|
String |
VT_BSTR |
String |
BSTR |
BSTR |
|
Time |
VT_DATE |
Date |
DATE |
DATE |
日設為1899年12月30日 |
Token |
VT_BSTR |
String |
BSTR |
BSTR |
不轉換和生效 |
unsignedByte |
VT_UI1 |
Byte |
unsigned char |
unsigned char |
|
UnsignedInt |
VT_DECIMAL |
Variant |
DECIMAL |
DECIMAL |
轉換時范圍生效 |
unsignedLong |
VT_DECIMAL |
Variant |
DECIMAL |
DECIMAL |
轉換時范圍生效 |
unsignedShort |
VT_UI4 |
Long |
Long |
Long |
轉換時范圍生效 |
XSD定義了兩套內建的數據類型:原始的和派生的。在下文中查閱內建數據類型的層次十分有益:
http://www.w3.org/TR/2001/PR-xmlschema-2-20010330 |
complex類型
XML schema允許complex類型的定義,就像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> |
不過,complex類型可以表達比struct更多的信息。除了<sequence>以外,它還可以有其他的子元素,比如<all>
<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> |
這意味著<element>的成員變量可以以任何順序排列,每一個都是可選的。這和C中的struct類型不太一樣。
注意內建數據類型string, int, float。C的string也是XML的string,float也類似。但C中的long類型在XML中是int(上表中)。
在WSDL文件中,像上面的complex類型可以在Types欄聲明。例如,我可以用以下方式聲明PERSON類型并用在Messages欄。
<?xml version="1.0" encoding="UTF-8" ?>
<definitions … >
<types>
<schema targetNamespace="someNamespace"
xmlns:typens="someNamespace" >
<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>
</schema>
</types>
<message name="addPerson">
<part name="person" type="typens:PERSON"/>
</message>
<message name="addPersonResponse">
<part name="result" type="xsd:int"/>
</message>
</definitions> |
上例中第一個消息由"adperson",并且有一個<part>,其類型為"PERSON"。PERSON類型是在Types欄聲明的。
如果我們使用完整的WSDL文件包含以上的部分,并以之初始化MSTK2 SoapClient,它將成功的解析該文件。當然,它不會去調用<addPerson>。這是因為SoapClient本身并不知道如何處理complex類型,它需要定制類型映射來處理complex類型。MSTK2文檔中有包含定制類型映射的示例。
還有另一種方法可以把<part>元素聯系到類型聲明。這就是使用元素。下例中我將Types欄中聲明兩個元素("Person"和"Gendr"),然后我將在"addPerson"<message>中使用元素屬性來引用它們。
<?xml version="1.0" encoding="UTF-8" ?>
<definitions … >
<types>
<schema targetNamespace="someNamespace"
xmlns:typens="someNamespace" >
<element name="Person">
<xsd:complexType>
<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>
</element>
<element name="Gender">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:enumeration value="Male" />
<xsd:enumeration value="Female" />
</xsd:restriction>
</xsd:simpleType>
</element>
</schema>
</types>
<message name="addPerson">
<part name="who" element="typens:Person"/>
<part name="sex" element="typens:Gender"/>
</message>
<message name="addPersonResponse">
<part name="result" type="xsd:int"/>
</message>
</definitions> |
Types欄中的Gender<element>里嵌入了枚舉類型,其枚舉值為"Male""Female"。然后我又在"addPerson"<message>中通過元素屬性而不是類型屬性來引用它。
"元素屬性"和"類型屬性"在把某特定類型關聯到<part>時有什么不同呢?使用元素屬性,我們可以描述一個部分,它可以假定幾個類型(就像變量一樣),而是用類型屬性我們就無法這樣做。下例說明了這一點。
<?xml version="1.0" encoding="UTF-8" ?>
<definitions … >
<types>
<schema targetNamespace="someNamespace"
xmlns:typens="someNamespace">
<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>
<xsd:complexType name="femalePerson">
<xsd:complexContent>
<xsd:extension base="typens:PERSON" >
<xsd:element name="favoriteLipstick" type="xsd:string" />
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:complexType name="malePerson">
<xsd:complexContent>
<xsd:extension base="typens:PERSON" >
<xsd:element name="favoriteShavingLotion" type="xsd:string" />
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:complexType name="maleOrFemalePerson">
<xsd:choice>
<xsd:element name="fArg" type="typens:femalePerson" >
<xsd:element name="mArg" type="typens:malePerson" />
</xsd:choice>
</xsd:complexType>
</schema>
</types>
<message name="addPerson">
<part name="person" type="typens:maleOrFemalePerson"/>
</message>
<message name="addPersonResponse">
<part name="result" type="xsd:int"/>
</message>
</definitions> |
上例也告訴我們extension的派生。"femailPerson"和"malePerson"都是從"PERSON"派生出來的。它們各有一些額外的元素:"femalePerson"有"favoriteLipstick"元素,"malePerson"有"favoriteShavingLotion"元素。兩派生類型都歸入一個complex類型"maleOrFemalePerson",使用的是<choice>構造。最后,在"adperson"<message>中,新類型有"person"<part>引用。這樣,參數或<part>就可以是"femalePerson"或"malePerson"了。
數組
XSD提供<list>結構來聲明一個數組,元素之間有空格界定。不過SOAP不是使用XSD來編碼數組的,它定義了自己的數組類型--"SOAP-ENC: Array"。下列的例子揭示了從這一類型派生出一位整數數組的方法:
<xsd:complexType name="ArrayOfInt">
<xsd:complexContent>
<xsd:restriction base="soapenc:Array">
<attribute ref="soapenc:arrayType" wsdl:arrayType="xsd:int[]"/>
</xsd:restriction>
</xsd:complexContent>
</xsd:complexType> |
新的complex類型從soapenc:array限制派生。然后又聲明了complex類型的一個屬性。引用"soapenc:arrayType"實際上是這樣完成的:
<xsd:attribute name="arrayType" type="xsd:string"/> |
wsdl:arrayType屬性值決定了數組每個成員的類型。數組的成員也可以是Complex類型。:
<xsd:complexType name="ArrayOfPERSON">
<xsd:complexContent>
<xsd:restriction base="soapenc:Array">
<attribute ref="soapenc:arrayType"
wsdl:arrayType="typens:PERSON[]"/>
</xsd:restriction>
</xsd:complexContent>
</xsd:complexType> |
WSDL要求數組的類型由"ArrayOf"和每個數組元素的類型串聯而成。很顯然,顧名思義,"ArrayOfPERSON"是PERSON結構的數組。下面我將使用ArrayOfPERSON來聲明一個<message>,并加入不止一個PERSON:
<?xml version="1.0" encoding="UTF-8" ?>
<definitions … >
<types>
<schema targetNamespace="someNamespace"
xmlns:typens="someNamespace" >
<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>
<xsd:complexType name="ArrayOfPERSON">
<xsd:complexContent>
<xsd:restriction base="soapenc:Array">
<attribute ref="soapenc:arrayType"
wsdl:arrayType="typens:PERSON[]"/>
</xsd:restriction>
</xsd:complexContent>
</xsd:complexType>
</schema>
</types>
<message name="addPersons">
<part name="person" type="typens:ArrayOfPERSON"/>
</message>
<message name="addPersonResponse">
<part name="result" type="xsd:int"/>
</message>
</definitions> |
<portType>和<operation>元素
PortType定義了一些抽象的操作。PortType中的operation元素定義了調用PortType中所有方法的語法,每一個operation元素聲明了方法的名稱、參數(使用<message>元素)和各自的類型(<part>元素要在所有<message>中聲明)。
在一篇WSDL文檔中可以有幾個<PortType>元素,每一個都和一些相關操作放在一起,就和COM和一組操作的接口相似。
在<operation>元素中,可能會有至多一個<input>元素,一個<output>元素,以及一個<fault>元素。三個元素各有一個名字和一個消息屬性。
<input>, <output>, <fault>元素屬性的名字有何含義呢?它們可以用來區別兩個同名操作(重載)。例如,看下面兩個C函數:
void foo(int arg);
void foo(string arg); |
這種重載在WSDL中可以這樣表示:
<?xml version="1.0" encoding="UTF-8" ?>
<definitions name="fooDescription"
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="foo1">
<part name="arg" type="xsd:int"/>
</message>
<message name="foo2">
<part name="arg" type="xsd:string"/>
</message>
<portType name="fooSamplePortType">
<operation name="foo" parameterOrder="arg " >
<input name="foo1" message="wsdlns:foo1"/>
</operation>
<operation name="foo" parameterOrder="arg " >
<input name="foo2" message="wsdlns:foo2"/>
</operation>
</portType>
<binding name="fooSampleBinding" type="wsdlns:fooSamplePortType">
<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>
<service name="FOOService">
<port name="fooSamplePort" binding="fooSampleBinding">
<soap:address
location="http://carlos:8080/fooService/foo.asp"/>
</port>
</service>
</definitions> |
到目前為止,還沒有一種SOAP的實現支持重載。這對基于JAVA的客戶端十分重要,因為JAVA服務器使用的接口用到JAVA的重載特性。而對基于COM的客戶端,就不那么重要,因為COM是不支持重載的。
<binding>和<operation>元素
Binding欄是完整描述協議、序列化和編碼的地方,Types, Messages和PortType欄處理抽象的數據內容,而Binding欄是處理數據傳輸的物理實現。Binding欄把前三部分的抽象定義具體化。
把相關的數據制定和消息聲明分開,這意味著同一類型服務的提供者可以把一系列的操作標準化。每個提供者可以提供定制的binding來互相區分。WSDL也有一個重要的結構,使抽象定義可以放在分離的文件中,而不是和Bindings和Services在一起,這樣可在不同的服務提供者之間提供標準化的抽象定義,這很有幫助。例如,銀行可以用WSDL文檔來標準化一些銀行的操作。每個銀行仍然可以自由的訂制下層的協議、串行優化,及編碼。
下面是重載的WSDL示例 的Binding欄,重復在此以便討論:
<binding name="fooSampleBinding" type="wsdlns:fooSamplePortType">
<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>元素已經取了一個名字(本例中"fooSampleBinding"),這樣就可以被Services欄的<port>元素引用了。它有一個"type"的屬性引用<portType>,本例中就是"wsdlns:fooSamplePortType"。第二行是MSTK2的擴展元素<stk:binding>,它指定了preferredEncoding屬性為"UTF-8"。
<soap:binding>元素指定了所使用的風格("rpc")和傳輸方式。Transport屬性應用了一個namespace,正是這個namespace指明使用HTTP SOAP協議。
有兩個同以"foo"命名的<operation>元素。唯一不同的是它們各自的<input>名字,分別為"foo1"和"foo2"。兩個<operation>元素中的<soap:operation>元素有同樣的"soapAction"屬性,是URI。soapAction屬性是SOAP特定的URI,它只是簡單的使用于SOAP消息。所產生的SOAP消息有一個SOAPAction頭,而URI也僅在<soap:operation>元素里才起作用。soapAction屬性在HTTP的binding中是必需的,但在其他非HTTP binding中卻不要提供。目前它的使用并不清楚,但它似乎有助于本例中的兩個"foo"操作。SOAP 1.1指明soapAction用來確定消息的"意圖"。似乎服務器可以在不解析整個消息的情況下就能使用這一屬性來發送消息。實際上,它的使用多種多樣。<soap:operation>元素也可以包含另一屬性,即"style"屬性,在有必要沖突<soap:binding>元素指定的風格時可以使用。
<operation>屬性可以包含<input>, <output> 和<fault>的元素,它們都對應于PortType欄中的相同元素。只有<input>元素在上例中提供。這三個元素中的每一個可有一個可選的"name"屬性,在本例中,我們用這種方法來區分同名操作。在本例的<input>元素中有一個<soap:body>元素,它指定了哪些信息被寫進SOAP消息的信息體中。該元素有以下屬性:
Use
用于制定數據是"encoded"還是"literal"。"Literal"指結果SOAP消息包含以抽象定義(Types, Messages, 和PortTypes)指定格式存在的數據。"Encoded"指"encodingStyle"屬性決定了編碼方式。
Namespace
每個SOAP消息體可以有其自己的namespace來防止命名沖突。這一屬性制定的URI在結果SOAP消息中逐字使用。
EncodingStyle
對SOAP編碼,它應該有以下URI值:
"http://schemas.xmlsoap.org/soap/encoding" |
文檔風格實現
在前幾欄中,<soap:binding>元素有一個類型屬性,設為"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和CompositeElement,還有一個類型聲明(CompositeType)。唯一聲明的<message>元素有四個部分:p1:Composite型;p2:int型;p3:SimpleElement型;p4:CompositeElement型。以下有一個表,對四種類型的use/type決定的binding作一比較:rpc/literal, document/literal, rpc/encoded, 以及document/encoded。表指明了每種binding的表現。
<service>和<port>元素
service是一套<port>元素。在一一對應形式下,每個<port>元素都和一個location關聯。如果同一個<binding>有多個<port>元素與之關聯,可以使用額外的URL地址作為替換。
一個WSDL文檔中可以有多個<service>元素,而且多個<service>元素十分有用,其中之一就是可以根據目標URL來組織端口。這樣,我就可以方便的使用另一個<service>來重定向我的股市查詢申請。我的客戶端程序仍然工作,因為這種根據協議歸類的服務不隨服務而變化。多個<service>元素的另一個作用是根據特定的協議劃分端口。例如,我可以把所有的HTTP端口放在同一個<service>中,所有的SMTP端口放在另一個<service>里。我的客戶可以搜索與它可以處理的協議相匹配的<service>。
<service name="FOOService">
<port name="fooSamplePort" binding="fooSampleBinding">
<soap:address
location="http://carlos:8080/fooService/foo.asp"/>
</port>
</service> |
在一個WSDL文檔中,<service>的name屬性用來區分不同的service。因為同一個service中可以有多個端口,它們也有"name"屬性。
總結
本文中我描述了WSDL文檔關于SOAP方面的最顯著的特點。不過應該說明的是WSDL并不僅限于HTTP上的SOAP。WSDL用來描述HTTP-POST、HTTP-GET、SMTP及其他協議時非常清晰。使用了WSDL,SOAP更加容易處理了,無論是開發者還是使用者。我相信WSDL和SOAP一起將會開創網絡應用程序世界的新時代。
WSDL的namespace里有一系列的XML元素。下表概述了那些元素、它們的屬性和內容。
元素 |
屬性 |
內容(子元素) |
<definitions> |
name
targetNamespace
xmlns (other namespaces) |
<types>
<message>
<portType>
<binding>
<service> |
<types> |
(none) |
<xsd:schema> |
<message> |
Name |
<part> |
<portType> |
Name |
<operation> |
<binding> |
name
type |
<operation> |
<service> |
name |
<port> |
<part> |
name
type |
(empty) |
<operation> |
name
parameterOrder |
<input>
<output>
<fault> |
<input> |
name
message |
(empty) |
<output> |
name
message |
(empty) |
<fault> |
name
message |
(empty) |
<port> |
name
binding |
<soap:address> |
資源:
1.
WSDL 1.1
2.
SOAP 1.1
3.
XML Schema Primer
4.
MS SOAP Toolkit Download Site
5.
A tool for translating IDL to WSDL
6.
Free Web Services resources including a WSDL to VB proxy generator
7.
PocketSOAP: SOAP related components, tools & source code