Web Service描述語言 WSDL 詳解

爲什麼使用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函數等效:

     

 int foo(int arg);


  這個例子足見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。

xmlns="http://www.w3.org/2001/XMLSchema"


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定義了兩套內建的數據類型:原始的和派生的。在下文中查閱內建數據類型的層次十分有益:
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 
發佈了22 篇原創文章 · 獲贊 6 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章