元數據實際上是服務終結點的描述,終結點由地址(Address)、綁定(Binding)和契約(Contract)經典的ABC三要素組成。認真閱讀過《WCF技術剖析(卷1)》的讀者相對會對這三要素的本質有一個深刻的認識:地址決定了服務的位置並實現相應的尋址機制;契約描述了消息交換模式(Message Exchange Pattern: MEP)以及消息的結構(Schema);綁定則通過創建信道棧實現對消息的編碼、傳輸和基於某些特殊的功能(比如實現事務、可靠傳輸以及基於消息的安全)對消息作出的處理。
服務的消費者通過獲取服務端發布的元數據,並在此基礎上重建終結點,才能取保請求:消息被發送到准確的目標地址;采用服務端期望的消息交換模式和並生成服務端能夠識別的消息結構;使用相匹配的消息編碼方式以確保服務端能夠對接收到的消息進行正常解碼;使用一致的傳輸協議以實現消息的正常傳輸;對消息進行與服務端一致性的處理以確保對事務、可靠傳輸、消息安全等協議的實現。
WCF是基於SOA構建的一個分布式通信平台,而SOA一個重要的特性就是實現跨平台的互操作。元數據是確保服務消費者正常調用目標服務(可能部署於異質平台),所以元數據本身需要采用一種開放的標准來表示。目前,元數據具有三種比較典型的表示方式:
XSD:通過XML Schema的形式描述組成消息的數據類型的XML結構;
WSDL:通過一個完整的Web Service Description Language文檔對服務進行全面的描述,即包括抽象的功能,也包括具體的細節;
WS-Policy策略:通過WS-Policy規范以斷言(Assertion)形式對服務能力和特性進行描述。
對跨平台互操作的實現不僅僅要求承載服務描述信息的元數據本身采用一種開放的標准或者規范來表示,甚至要求元數據交換(Metadata Exchange:MEX)同樣按照廠商共同遵守的規范來進行。在WS-*規范體系中,WS-Metadata Exchange(WS-MEX)為元數據的交換進行了標准化的規范。WS-MetadataExchange(以下簡稱WS-MEX)規范了與終結點(這裡是廣義的Web服務終結點,與具體的技術無關)如何表示成一個WS-Transfer資源,並被嵌入到WS-Addressing終結點引用(Endpoint Reference)中,以及元數據如何被相應的Web服務終結點獲取。簡言之,WS-MEX是一個關於如何進行元數據交換的WS規范。WS-MEX和其他的WS-*規范一起,比如WSDL、WS-Addressing、WS-Transfer、WS—Policy等一起組成了一個完整的描述Web服務元數據和元數據交換的規范體系,在正式介紹WS-MEX之前,先來大概了解一些其他的這些輔助性WS-*規范。
一、WS-Policy
一個Web服務(這裡指廣義的、與技術平台無關的Web服務)除了實現通過服務契約定義的業務功能之外,為了實現一些額外的功能(比如安全、事務和可靠傳輸等),還需要具有一些與業務無關的行為(Behavior)和能力(Capability),我們可以將這些統稱為Web服務的策略(Policy)。WS-Policy提供了一個基於XML的框架模型和語法用於描述Web服務的能力、要求和行為屬性。
WS-Policy屬於WS-*體系中的一個基礎性規范,其規范本身不會被單獨使用,而是服務於其他的WS規范(我們一般稱這些為Domain Specific規范,比如WS-Transaction、WS-Reliable Messaging和WS-Security等)提供一種統一的策略描述。
W3C先後在2006年和2007年推出了兩個版本的WS-Policy規范,即WS-Policy 1.2和WS-Policy 1.5。在這裡,我們僅僅是正對最新的WS-Policy版本(1.5)來簡單介紹一些一個完整的WS策略具有怎樣的結構,對於希望深入了解WS-Policy的讀者,可以通過後面的地址下載到W3C的官方文檔:http://www.w3.org/TR/ws-policy/。
WS-Policy采用一個基於XML的策略表達式(Policy Expression)表示一個策略,在投入到對具體策略定義的介紹之前,我們不妨先來看看一個典型的策略表達式的定義。下面XML片斷表示的策略表達式來自於WS-Security,這是一個基於如何實現基於消息安全的WS規范。它體現的含義是:對消息的主題部分采用簽名還是加密。
1: (01)<wsp:Policy
2: xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702"
3: xmlns:wsp="http://www.w3.org/ns/ws-policy" >
4: (02) <wsp:ExactlyOne>
5: (03) <wsp:All>
6: (04) <sp:SignedParts>
7: (05) <sp:Body/>
8: (06) </sp:SignedParts>
9: (07) </wsp:All>
10: (08) <wsp:All>
11: (09) <sp:EncryptedParts>
12: (10) <sp:Body/>
13: (11) </sp:EncryptedParts>
14: (12) </wsp:All>
15: (13) </wsp:ExactlyOne>
16: (14) </wsp:Policy>
上面這個XML采用一種標准的形式(WS-Policy還定義了一種簡寫的策略表示方式)定義一個策略,接下來我們就此為基礎介紹一個完整的策略表達式具有怎樣的結構。
在一個基於Web服務的系統中,策略體現的對Web服務相關實體要求(Requirements)、能力(Capabilities)和特性(Characteristics)等行為屬性的表示。WS-Policy通過斷言(Assertion)的形式來表示這些單一的行為屬性,然後通過一定的規則將相關的策略斷言有機的組合在一起,以實現對整個Web服務目標實體的完整描述。
按照WS-Policy 1.5的規定,所有的策略元素均定義在http://www.w3.org/ns/ws-policy命名空間下,一個完整策略通過一個基於XML的策略表達式描述。1、
1、策略表達式(Policy Expression)
一個策略表達式是一個XML信息集(XML InfoSet),描述了一個完整的策略。策略表達式具有兩個不同的表示形式:標准形式(Normal Form)和簡寫形式(Compact Form)。一個策略表達式是一個策略選擇項(Policy Alternative)的集合。我們要求滿足某個策略,意味著我們須要滿足該集合的至少一個策略選擇項。
2、策略選擇項(Policy Alterative)
一般情況下,策略往往是承載一些確保服務正常調用(說得更加具體一點,就是確保Web服務終結點能夠正常交互)的條件信息。服務的消費者在進行正常的服務消費之前,需要保證提供這些必備的前提條件。對於服務提供者來說,針對某個具體的應用場景的時候,會提供一到多個不同的選擇項。比如我們上面給出的例子,在應用基於消息的安全策略的時候,根據不同安全級別的需求,可以選擇對消息的主體部分進行簽名或者加密。
這些單一的選擇項被稱為策略選擇項(Policy Alternative),對於上面給出的策略表達式,(03)-(07)和(08)-(12)定義兩個策略選擇項,代表對消息主體進行簽名還是加密。一個策略選擇項由零到多個策略斷言通過相應的策略操作符組合在一起。
3、策略斷言(Policy Assertion)
在WS-Policy規范下,Web服務實體某個單一的行為屬性最終通過一個策略斷言表示。而一個策略斷言由一個比如的斷言類型(Assert Type)和一組可選的斷言參數(Assert Parameter)組成。斷言類型通過一個有效名稱(Qualified Name: QName)表示,即命名空間和本地名稱(Local Name)的組合。在定義了兩個策略斷言:(04)-(06)和(09)-(11),其斷言類型分別為:sp:SignedParts和sp:EncryptedParts。
一個最簡單的策略斷言可以僅僅由一個包含斷言類型的空XML元素構成,我們也可以為這個XML元素添加用於輔助描述該斷言的XML屬性(Attribute)和XML子元素,我們把這些策略輔助描述信息稱為斷言參數。
一個策略斷言可以很簡單(一個空XML元素),也可以定義的很復雜,這取決於具體的策略描述對象。一個比較極端的策略斷言是:將一個完整的策略表達式作為其子元素,我們把這種情況稱為策略斷言嵌套(Policy Assetion Nesting)。嵌套的策略斷言的結構可以通過下面的XML表示:
1: (01) <Assertion …>
2: (02) …
3: (03) ( <wsp:Policy …> … </wsp:Policy> )?
4: (04) …
5: (05) </Assertion>
4、策略操作符(Policy Operator)
一個策略選擇項又由零(也就是說可以定義空的策略選擇項)到多個策略斷言通過一定的規則構成,在這裡對策略斷言的組合規則通過策略操作符來體現。策略操作符體現的是這樣一種含義:請求者采用怎樣的方式去滿足構成策略選擇項的所有策略斷言,需要滿足所有的斷言呢,還是僅僅需要滿足其中某一個?
WS-Policy定義了兩種主要的策略操作符(實際上Policy本身就屬於一個策略操作符)ExactlyOne和All。根據名稱我們不難猜出,ExactlyOne表示僅僅需要滿足斷言集合的某一個元素即可,而All意味著需要滿足斷言集合中的所有元素。
此外,由於wsp:ExactlyOne表示的是“滿足其中之一”的意思,這和策略和策略選擇項之間體現的關系吻合,所以在標准形式體現的策略表達式中,所有的策略選擇項均納於wsp:ExactlyOne操作符之中。
圖1 策略表達式結構圖
整個策略表達式的結構,即策略、策略選擇項和策略斷言之間的關系,大體可以通過圖1表示。關於WS-Policy中對策略表達式的規定,還一個其他一些額外的內容,比如策略的識別(Policy Identifying)、策略簡寫形式(Compact Form)等,在這裡就不再一一介紹了,有興趣的讀者可以下載WS-Policy 1.5的官方文檔(http://www.w3.org/TR/ws-policy/)。
二、WS-Transfer
在Web服務的世界中,很多資源(Resource)都可以通過XML的形式來表示,並通過WS-Addressing規范的方式進行尋址。而WS-Transfer就是這樣的一個WS規范:規定如何采用基於SOAP的方式實現可尋址的(Addressable)Web服務資源的獲取、更新、刪除和創建。接下來對WS-Transfer的介紹,假設你對WS-Addressing和SOAP有了一個基本的了解。對這兩個規范不是很熟悉的讀者,可以從W3C的網站上下載官方文檔。此外,在《WCF技術剖析(卷1)》的第2章和第6章對WS-Addressing 1.0和SOAP 1.2進行了概括性的介紹。
雖然我們可以通過很多不同的方式(比如REST)對資源進行獲取和更新,WS-Transfer完全建立在基於SOAP基礎上。WS-Transfer的主要內容集中在對4個基本資源操作上面:Get、Put、Delete和Create,分別實現對資源的獲取、更新、刪除和創建。W3C分別在2006年3月份和9月份先後推出了兩個版本的WS-Transfer,我們接下來的介紹完全基於最新版本的WS-Transfer,你可以從後面的地址獲取正式的官方文檔:http://www.w3.org/Submission/WS-Transfer/。
所以基於WS-Transfer元素定義在http://schemas.xmlsoap.org/ws/2004/09/transfer命名空間下。接下來我們著重對上述的4個資源進行介紹。
1、資源的獲取:Get
請求者創結基於Get操作的SOAP消息像目標地址發送請求以獲取相應的資源,這樣的請求消息必須具有如下的格式。
1: <s:Envelope …>
2: <s:Header …>
3: <wsa:Action>
4: http://schemas.xmlsoap.org/ws/2004/09/transfer/Get
5: </wsa:Action>
6: <wsa:MessageID>xs:anyURI</wsa:MessageID>
7: <wsa:To>xs:anyURI</wsa:To>
8: …
9: </s:Header>
10: <s:Body ...>
11: …
12: </s:Body>
13: </s:Envelope>
WS-Addressing報頭Action的值必須是http://schemas.xmlsoap.org/ws/2004/09/transfer/Get,消息的主體(Body)部分為空。資源接受Get請求後,采用如下結構的SOAP消息進行回復,Action報頭為http://schemas.xmlsoap.org/ws/2004/09/transfer/GetResponse,表示資源的XML必須作為消息主體的第一個子元素。
1: <s:Envelope …>
2: <s:Header …>
3: <wsa:Action>
4: http://schemas.xmlsoap.org/ws/2004/09/transfer/GetResponse
5: </wsa:Action>
6: <wsa:RelatesTo>xs:anyURI</wsa:RelatesTo>
7: <wsa:To>xs:anyURI</wsa:To>
8: …
9: </s:Header>
10: <s:Body …>
11: xs:any
12: …
13: </s:Body>
14: </s:Envelope>
2、資源的更新:Put
請求者創建基於Put操作的SOAP消息,提供新的資源內容向目標地址發送請求,以更新某個現有的資源。Put請求消息必須具有如下的格式:
1: <s:Envelope …>
2: <s:Header …>
3: <wsa:Action>
4: http://schemas.xmlsoap.org/ws/2004/09/transfer/Put
5: </wsa:Action>
6: <wsa:MessageID>xs:anyURI</wsa:MessageID>
7: <wsa:To>xs:anyURI</wsa:To>
8: …
9: </s:Header>
10: <s:Body…>
11: xs:any
12: ...
13: </s:Body>
14: </s:Envelope>
Put請求消息的Action報頭為http://schemas.xmlsoap.org/ws/2004/09/transfer/Put,表示資源的XML必須作為消息主體的第一個子元素。資源接受Put請求後,通過具有如下格式的SOAP回復請求。回復消息的Action報頭為http://schemas.xmlsoap.org/ws/2004/09/transfer/PutResponse,如何完全采用請求者提供的資源對現有的目標資源進行更新,那麼回復消息的主體部分為空,否則將更新後的資源以XML的形式置於回復消息主體部分的第一個子元素中。
1: <s:Envelope …>
2: <s:Header …>
3: <wsa:Action>
4: http://schemas.xmlsoap.org/ws/2004/09/transfer/PutResponse
5: </wsa:Action>
6: <wsa:RelatesTo>xs:anyURI</wsa:RelatesTo>
7: <wsa:To>xs:anyURI</wsa:To>
8: …
9: </s:Header>
10: <s:Body …>
11: xs:any ?
12: </s:Body>
13: </s:Envelope>
3、資源的刪除:Delete
請求者創建基於Delelte操作的SOAP消息向目標地址發送請求,以刪除某個現有的資源。Delelte請求消息必須具有如下的格式:
1: <s:Envelope …>
2: <s:Header …>
3: <wsa:Action>
4: http://schemas.xmlsoap.org/ws/2004/09/transfer/Delete
5: </wsa:Action>
6: <wsa:MessageID>xs:anyURI</wsa:MessageID>
7: <wsa:To>xs:anyURI</wsa:To>
8: …
9: </s:Header>
10: <s:Body … />
11: </s:Envelope>
Delete請求消息的Action報頭為http://schemas.xmlsoap.org/ws/2004/09/transfer/Delete,消息主體部分為空。資源接受Delete請求後,通過具有如下格式的SOAP回復請求。回復消息的Action報頭為http://schemas.xmlsoap.org/ws/2004/09/transfer/DeleteResponse,回復消息的主體部分為空。
4、資源的創建:Create
請求者創建基於Create操作的SOAP消息向目標地址發送請求,以創建一個新的資源。Create請求消息必須具有如下的格式:
1: <s:Envelope …>
2: <s:Header …>
3: <wsa:Action>
4: http://schemas.xmlsoap.org/ws/2004/09/transfer/Create
5: </wsa:Action>
6: <wsa:MessageID>xs:anyURI</wsa:MessageID>
7: <wsa:To>xs:anyURI</wsa:To>
8: …
9: </s:Header>
10: <s:Body …>
11: xs:any
12: ...
13: </s:Body>
14: </s:Envelope>
Create請求消息的Action報頭為http://schemas.xmlsoap.org/ws/2004/09/transfer/Create,新的資源內容以XML的形式作為消息主體部分的第一個子元素。資源工廠接受Create請求後,通過具有如下格式的SOAP回復請求。回復消息的Action報頭為http://schemas.xmlsoap.org/ws/2004/09/transfer/CreateResponse,並將新創建資源的WS-Addressing的終結點引用(Endpoint Reference)作為回復消息主體部分的第一個子元素。如果,最終被更新的資源內容和請求者提供的不一致,本更新的資源內容需要作為回復消息主體部分的第二個子元素返回。
1: <s:Envelope …>
2: <s:Header …>
3: <wsa:Action>
4: http://schemas.xmlsoap.org/ws/2004/09/transfer/CreateResponse
5: </wsa:Action>
6: <wsa:RelatesTo>xs:anyURI</wsa:RelatesTo>
7: <wsa:To>xs:anyURI</wsa:To>
8: …
9: </s:Header>
10: <s:Body …>
11: <wxf:ResourceCreated>endpoint-reference</wxf:ResourceCreated>
12: xs:any?
13: </s:Body>
14: </s:Envelope>
三、 WSDL
WSDL,全稱Web服務描述語言(Web Service Description Language),是采用XML格式的形式對Web服務的描述。WSDL將一個Web服務定義成一組終結點的集合,而每一個終結點包含一系列基於消息(Message)的操作(Operation)。這些抽象的操作和消息最終和相應的協議以及消息格式綁定。
雖然W3C在2007年6月份就正式出台了WSDL 2.0版本,並將其作為官方推薦,但是該版本並沒有得到廣泛的推廣,並沒有被主流的廠商完全支持。如今,WCF完全支持的還是WSDL 1.1版本,所以接下來我們將針對這個版本對WSDL作一個簡單的介紹,對於希望了解WSDL1.1的讀者可以從後面的地址下載官方文檔:http://www.w3.org/TR/wsdl。就WSDL描述對象的性質,我們大體可以將所有WSDL的元素劃分為以下兩類:
抽象元素:比如通過XSD表示的數據類型;用於承載數據信息的消息;通過對關聯的消息按照某種消息交換模式組合而成的操作等;
具體元素:比如將相應的操作和具體的網絡協議和消息格式進行綁定等。
為了有效地了解WSDL的結構,我們首先來看看一段直接從官方文檔上拷貝出來的WSDL文檔:
1: <?xml version="1.0"?>
2: <definitions name="StockQuote" targetNamespace="http://example.com/stockquote.wsdl" xmlns:tns="http://example.com/stockquote.wsdl" xmlns:xsd1="http://example.com/stockquote.xsd" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns="http://schemas.xmlsoap.org/wsdl/">
3: <types>
4: <schema targetNamespace="http://example.com/stockquote.xsd" xmlns="http://www.w3.org/2000/10/XMLSchema">
5: <element name="TradePriceRequest">
6: <complexType>
7: <all>
8: <element name="tickerSymbol" type="string"/>
9: </all>
10: </complexType>
11: </element>
12: <element name="TradePrice">
13: <complexType>
14: <all>
15: <element name="price" type="float"/>
16: </all>
17: </complexType>
18: </element>
19: </schema>
20: </types>
21: <message name="GetLastTradePriceInput">
22: <part name="body" element="xsd1:TradePriceRequest"/>
23: </message>
24: <message name="GetLastTradePriceOutput">
25: <part name="body" element="xsd1:TradePrice"/>
26: </message>
27: <portType name="StockQuotePortType">
28: <operation name="GetLastTradePrice">
29: <input message="tns:GetLastTradePriceInput"/>
30: <output message="tns:GetLastTradePriceOutput"/>
31: </operation>
32: </portType>
33: <binding name="StockQuoteSoapBinding" type="tns:StockQuotePortType">
34: <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
35: <operation name="GetLastTradePrice">
36: <soap:operation soapAction="http://example.com/GetLastTradePrice"/>
37: <input>
38: <soap:body use="literal"/>
39: </input>
40: <output>
41: <soap:body use="literal"/>
42: </output>
43: </operation>
44: </binding>
45: <service name="StockQuoteService">
46: <documentation>My first service</documentation>
47: <port name="StockQuotePort" binding="tns:StockQuoteBinding">
48: <soap:address location="http://example.com/stockquote"/>
49: </port>
50: </service>
51: </definitions>
上面給出的是一個很標准的WSDL文檔,從中我們可以看出它由五個子元素構成:Type、Message、PortType、Binding和Service。這五大元素構成一個了一個完成得WSDL,現在可以就對它們逐個進行介紹。
1、Types:通過XSD表示的數據類型的集合
WSDL並沒有屬於自己的數據類型定義規范,而是直接采用XSD作為數據定義的語言。上面的WSDL文檔通過XSD定義了兩個XML元素,元素名稱分別為TradePriceRequest和TradePrice,命名空間為http://example.com/stockquote.xsd。從XSD的定義我們不難看出,這兩個類型分別是字符串和浮點數類型。
1: <types>
2: <schema targetNamespace="http://example.com/stockquote.xsd" xmlns="http://www.w3.org/2000/10/XMLSchema">
3: <element name="TradePriceRequest">
4: <complexType>
5: <all>
6: <element name="tickerSymbol" type="string"/>
7: </all>
8: </complexType>
9: </element>
10: <element name="TradePrice">
11: <complexType>
12: <all>
13: <element name="price" type="float"/>
14: </all>
15: </complexType>
16: </element>
17: </schema>
18: </types>
2、Message: 通信數據的載體
Web服務采用基於消息的通信方式,所以消息是通信數據的載體。WSDL的message元素用於定義所有定義終結點操作的消息的結構。WSDL的消息是一個具有唯一標識(通過Name屬性)的XML元素,通常利用Types結點中定義的數據類型來描述。上面的WSDL定義了兩個消息,名稱分別為GetLastTradePriceInput和GetLastTradePriceOutput,消息主體部分的結構通過引用定義在Types結點中的XML元素的有效名稱(QName:命名空間+本地名稱)。
1: <message name="GetLastTradePriceInput">
2: <part name="body" element="xsd1:TradePriceRequest"/>
3: </message>
4: <message name="GetLastTradePriceOutput">
5: <part name="body" element="xsd1:TradePrice"/>
6: </message>
3、PortType:相關操作的集合
一個服務邏輯上有一系列關聯的操作組成,從消息交換的角度上講,操作進行關聯的消息按照相應的消息交換模式的有機組合。WSDL的PortType表示的就是這麼一個操作的集合,反映在XML結構上,就是一組operation元素的基本。每一個operation XML元素代表一個單一的操作,它通過一個或者多個消息組合而成。消息的不同組合方式反映了操作采用的不同消息交換模式(MEP: Message Exchange Pattern)。上面給出的WSDL通過如下的XML片斷定義了一個僅僅包含一個操作的PortType。
1: <portType name="StockQuotePortType">
2: <operation name="GetLastTradePrice">
3: <input message="tns:GetLastTradePriceInput"/>
4: <output message="tns:GetLastTradePriceOutput"/>
5: </operation>
6: </portType>
在《WCF技術剖析(卷1)》第4章對服務操作的介紹中,我們說服務契約中的操作契約本質上就是定義了操作采用的消息交換模式,以及消息的格式。在這裡你會進一步得到證實,實際上,當某個WCF服務通過WSDL的形式發布出來,服務契約映射的部分就是PortType。WCF支持三種典型的消息交換模式:單工(One-way)請求回復(Request-Reply)和雙工(Duplex)。實際上,雙工模式是由前面兩種模式組合而成,單工(One-way)和請求-回復(Request-Reply)模式才是基元消息交換模式。除了這兩種基元模式,WSDL還對另外兩種消息交換模式提供支持:懇請-回復(Solicit-Response)和通知(Notification)模式。
PortType中的每一個操作均由輸入(Input)和輸出(Output)消息的不同組合方式定義,而這種對輸入、輸出消息的不同組合就是對某種消息交換模式的反映。接下來,我們站在服務端終結點的角度,來介紹上述的4中消息交換模式:
單工(One-way)
單工消息交換模式下,終結點僅僅是接收來自客戶端的請求。單向操作僅僅包含一個輸入消息,在WSDL中的表示如下:
1: <wsdl:definitions ... >
2: <wsdl:portType ... >*
3: <wsdl:operation name="nmtoken">
4: <wsdl:input name="nmtoken"? message="qname"/>
5: </wsdl:operation>
6: </wsdl:portType >
7: </wsdl:definitions>
請求-回復(Request-Reply)
請求-回復消息交換模式下,終結點接收來自客戶端的請求,並向發送匹配的回復消息。請求-恢復操作通過輸入消息和輸出消息的有序組合表示,在WSDL中的表示如下:
1: <wsdl:definitions …>
2: <wsdl:portType …>*
3: <wsdl:operation name="nmtoken" parameterOrder="nmtokens">
4: <wsdl:input name="nmtoken"? message="qname"/>
5: <wsdl:output name="nmtoken"? message="qname"/>
6: <wsdl:fault name="nmtoken" message="qname"/>*
7: </wsdl:operation>
8: </wsdl:portType>
9: </wsdl:definitions>
懇請-回復(Solicit-Response)
在懇請-回復消息交換模式下,終結點先向客戶端到發送請求,並接收來自客戶端的回復,這和請求-回復模式正好相反。懇請-回復操作由輸出消息和輸入消息的有序組合表示,在WSDL中的表示如下:
1: <wsdl:definitions …>
2: <wsdl:portType …>*
3: <wsdl:operation name="nmtoken" parameterOrder="nmtokens">
4: <wsdl:output name="nmtoken"? message="qname"/>
5: <wsdl:input name="nmtoken"? message="qname"/>
6: <wsdl:fault name="nmtoken" message="qname"/>*
7: </wsdl:operation>
8: </wsdl:portType>
9: </wsdl:definitions>
通知(Notification):
在通知消息交換模式下,終結點僅僅向客戶端發送請求,這和單向模式正好相反。通知操作由單一的輸出消息組成,在WSDL中的表示如下:
1: <wsdl:definitions …>
2: <wsdl:portType …> *
3: <wsdl:operation name="nmtoken">
4: <wsdl:output name="nmtoken"? message="qname"/>
5: </wsdl:operation>
6: </wsdl:portType>
7: </wsdl:definitions>
注:對於請求-回復和懇請-回復消息交換模式來說,雙方除了進行正常的消息交換之外,當錯誤發生,需要將錯誤信息封裝成消息發送給對方。所以,這兩種類類型的操作除了輸出消息和輸入消息的描述之外,還具有錯誤消息的描述。錯誤消息在操作中通過<wsdl:fault/〉表示。
4、Bindings:消息、操作與協議、格式的綁定
上面介紹WSDL的三個元素主要從抽象的角度對數據類型、消息和操作進行描述,要創建服務於具體消息交換場景的終結點,還需要將這需抽象的描述和具體的消息格式(Format)和網絡協議綁定,比如SOAP、HTTP-GET和MIME等。
在這裡,我們很有必要強調“終結點”,本節我們提到的終結點在大部分場景中都是指與技術無關的、用於進行消息交換的“端口”。而我們WCF中提到的終結點,可以看成是這樣一個通用的終結點在具技術平台中的實現。WCF的終結點由地址、綁定和契約構成,結合WSDL我們不難看出,Type、Message和PortType是對契約的描述。而綁定,就其語言和功能上講,就是實現了抽象的描述和具體的協議(網絡傳輸協議、SOAP和WS-*規范等)之間的綁定。在WSDL中,Bindings元素具有一樣的功能。
在WSDL中,我們可以通過很多綁定擴展實現與某種協議的綁定,而最為常見的是基於SOAP 1.1和SOAP 1.2的綁定。上面給出的WSDL中定義了一個典型的基於SOAP 1.1的綁定(SOAP 1.1和SOAP 1.2綁定的命名空間分別為http://schemas.xmlsoap.org/wsdl/soap/和http://schemas.xmlsoap.org/wsdl/soap12/)。
1: <binding name="StockQuoteSoapBinding" type="tns:StockQuotePortType">
2: <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
3: <operation name="GetLastTradePrice">
4: <soap:operation soapAction="http://example.com/GetLastTradePrice"/>
5: <input>
6: <soap:body use="literal"/>
7: </input>
8: <output>
9: <soap:body use="literal"/>
10: </output>
11: </operation>
12: </binding>
以上面這個SOAP綁定為例,一個綁定通過name屬性(name="StockQuoteSoapBinding")定義其唯一標識,並通過type屬性和定義個PortType(type="tns:StockQuotePortType")進行關聯。該SOAP綁定定義了消息采用的風格(style="document",另外一個選項是“rpc”)和傳輸協議(transport="http://schemas.xmlsoap.org/soap/http")。在操作級別,定義了操作反映在SOAP Action報頭的值(soapAction="http://example.com/GetLastTradePrice"),以及輸入和輸出消息的主體部分采用的編碼方式(use="literal")。
5、Service:相關終結點的集合
由於一個Web服務最終以終結點的方式暴露出來,所以WSDL最終體現在對終結點集合的描述,這裡介紹的WSDL最後一個元素<Service/>本質上就是對基於該Web服務的一組相關終結點的定義。我們照例將上面給出的WSDL的Service相關部分提取出來,根據具體的例子分析Service結點應有的結構。
1: <service name="StockQuoteService">
2: <documentation>My first service</documentation>
3: <port name="StockQuotePort" binding="tns:StockQuoteBinding">
4: <soap:address location="http://example.com/stockquote"/>
5: </port>
6: </service>
<Service>通過name屬性(name="StockQuoteService")定義了服務的唯一標識。該節點具有一個可選的<documentation>元素,可以通過單純的文本或者XML為該服務定義一些可讀的說明性的描述。<Service>結點最重要的就是一組<port>元素,而每一個port即代表著一個終結點。每個具有一個name屬性定義終結點的名稱,通過binding屬性引用相應的定義得binding。並通過相應的綁定擴展定義終結點的地址,在這裡終結點的地址通過SOAP綁定定義(<soap:address location="http://example.com/stockquote"/>)。
圖2反映了WSDL5個元素之間的關系。對於表示一個終結點對象<port>元素來說,它具有一個地址(Address)和關聯著一個綁定(Binding),而綁定對象關聯著一個PortType。而一個PortType實際上對應的著WCF中的契約(Contract)。所以,WCF下的終結點由地址、綁定和契約三要素組成在這裡也得到進一步的反映,實際上,WCF本身就是按照WS開放標准設計的。
圖2 WSDL5個元素之間的引用關系
四、 WS-MEX
WCF的元數據結構體系構建在一個開放的標准之上,這個標准就是WS-Metadata Exchange,簡稱WS-MEX。WS-MEX是WS-*大家庭中的一名重要成員,最新的版本是1.1,希望全面了解WS-MEX 1.1規范的讀者可以從後面的地址下載官方文檔:http://specs.xmlsoap.org/ws/2004/09/mex/WS-MetadataExchange.pdf。規范了如何將基於Web終結點的元數據表示成一個WS-Transfer資源;如何將元數據內嵌於WS-Addressing的終結點引用(Endpoint Reference)中;以及如何獲取某個Web服務終結點的元數據。WS-MEX主要的目的在規范元數據的獲取,它提供了如下兩種不同的方式去獲取Web服務終結點的元數據:WS-Transfer Get和Get Metadata。此外,基於WS-MEX的元素定義在http://schemas.xmlsoap.org/ws/2004/09/mex命名空間下。
1、通過WS-Transfer GET操作獲取元數據
我之所以在正式介紹WS-MEX之前會先對WS-Transfer作一個簡單的討論,使因為采用WS-Transfer的Get操作是元數據的主要獲取方式之一。通過前面的介紹,我們知道了WS-Transfer旨在規范如何獲取、更新、刪除和創建Web服務資源。元數據本身就可以作為一種典型的Web服務資源,那麼采用WS-Transfer無疑是一種最直接的選擇。
元數據的提供者將元數據作為一種Web服務資源通過一個基於WS-Transfer的終結點暴露出來,請求者向該終結點發送WS-Transfer Get請求,以回復消息的形式獲得所需的元數據。下面就是一個典型的基於SOAP 1.1的WS-Transfer Get請求消息,請求的目標地址就是元數據資源對應的終結點。
1: <s11:Envelope xmlns:s11="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsa10="http://www.w3.org/2005/08/addressing">
2: <s11:Header>
3: <wsa10:Action>http://schemas.xmlsoap.org/ws/2004/09/transfer/Get</wsa10:Action>
4: <wsa10:To>http://services.example.org/stockquote/metadata</wsa10:To>
5: <wsa10:ReplyTo>
6: <wsa10:Address>http://client.example.org</wsa10:Address>
7: </wsa10:ReplyTo>
8: <wsa10:MessageID>urn:uuid:1cec121a-82fe-41da-87e1-3b23f254f128</wsa10:MessageID>
9: </s11:Header>
10: <s11:Body />
11: </s11:Envelope>
針對上面一個基於WS-Transfer Get操作的元數據請求,可能會得到如下一個標准的WS-Transfer Get回復消息,請求的元數據被置於SOAP消息的主體部分。
1: <s11:Envelope xmlns:s11="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsa10="http://www.w3.org/2005/08/addressing" xmlns:mex="http://schemas.xmlsoap.org/ws/2004/09/mex" xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
2: <s11:Header>
3: <wsa10:Action> http://schemas.xmlsoap.org/ws/2004/09/transfer/GetResponse</wsa10:Action>
4: <wsa10:To>http://client.example.org</wsa10:To>
5: <wsa10:RelatesTo>urn:uuid:1cec121a-82fe-41da-87e1-3b23f254f128</wsa10:RelatesTo>
6: </s11:Header>
7: <s11:Body>
8: <mex:Metadata>
9: <mex:MetadataSection Dialect="http://schemas.xmlsoap.org/wsdl/">
10: <wsdl:definitions name="StockQuote" ...>
11: ...
12: </wsdl:definitions>
13: </mex:MetadataSection>
14: <mex:MetadataSection Dialect="http://www.w3.org/2001/XMLSchema" Identifier="http://services.example.org/stockquote/schemas">
15: <mex:Location>http://services.example.org/stockquote/schemas</mex:Location>
16: </mex:MetadataSection>
17: <mex:MetadataSection Dialect="http://schemas.xmlsoap.org/ws/2004/09/policy" Identifier="http://services.example.org/stockquote/policy">
18: <mex:MetadataReference>
19: <wsa10:Address>http://services.example.org/stockquote/policy</wsa10:Address>
20: </mex:MetadataReference>
21: </mex:MetadataSection>
22: </mex:Metadata>
23: </s11:Body>
24: </s11:Envelope>
從上面提供的包含元數據的SOAP消息中,我們可以看到:所有的元數據均包含在<Metadata>結點中。<Metadata>結點實際上是一個<MetadataSection>元素的集合,具體的元數據定一個在相應的<MetadataSection>結點下,元數據的表現形式通過Dialect屬性定義。通過Dialect表示的元數據表示形式被稱為元數據方言。
元數據方言(Dialect)
在本章開始的時候,我們就談到Web服務終結點元數據具有三種典型的表現形式:WSDL、XSD和WS-Policy。在看看我們上面給出的包含元數據的SOAP消息中,<Metadata>結點下三個<MetadataSection>分別就對應這三種形式的元數據。第一個<MetadataSection>通過內聯的方式直接嵌入一個WSDL文檔;第二個<MetadataSection>以地址的方式指定了一個XML Schema;第三個<MetadataSection>以終結點引用的方式指定了一個WS-Policy策略。<MetadataSection>的Dialect以一個決定地址的形式指明了元數據體現的形式,即元數據方言(Dialect)。圖3展現了Metadadata、MetadataSection以及這三種典型元數據方言之間的關系。在WS-MEX中為以下5種方言定義了相應的URI:
XML Schema:http://www.w3.org/2001/XMLSchema
WSDL:ttp://schemas.xmlsoap.org/wsdl/
WS-Policy:http://schemas.xmlsoap.org/ws/2004/09/policy
WS-Policy Attachment:http://schemas.xmlsoap.org/ws/2004/09/policy/attachment
MEX:http://schemas.xmlsoap.org/ws/2004/09/mex
注:WS-Policy Attachment是另外一份WS規范,用於如何將WS-Policy中定義的策略和具體的WS規范(Domain-Specific WS Specification)進行關聯。MEX代表一般意義的元數據,它包含基於某種具體方言的元數據。
圖3 Metata、MetadataSection與三種典型的元數據表現形式
2、通過Get Metadata操作獲取元數據
通過WS-Transfer Get的方式獲取Web服務終結點的元數據的前提是直接將元數據本身作為Web服務資源。但是對於某些特殊的場景,這種方式不不太適合。比如,多個元數據資源關聯到同一個元數據終結點,希望通過向該終結點發送請求獲取所有相關的元數據;而且,並不是在任何情況下都能將終結點的元數據作為一個可以被尋址(基於WS-Addressing)的Web服務資源。為了在這些場景中解決元數據的獲取,WS-MEX提出了另一種替換的元數據獲取方式:Get Metadata。Get Metadata操作請求的SOAP消息具有如下的結構要求(?表示0或1個前置元素):
1: [action]
2: http://schemas.xmlsoap.org/ws/2004/09/mex/GetMetadata/Request
3: [Body]
4: <mex:GetMetadata ...>
5: (<mex:Dialect>xs:anyURI</mex:Dialect>
6: (<mex:Identifier>xs:anyURI</mex:Identifier>)?
7: )?
8: </mex:GetMetadata>
當服務終結點接受了Get Metadata請求後,生成相應的回復消息並將元數據置於消息的主體部分。Get Metadata回復消息具有如下的結構要求:
1: [action]
2: http://schemas.xmlsoap.org/ws/2004/09/mex/GetMetadata/Response
3: [Body]
4: <mex:Metadata ...>
5: ...
6: </mex:Metadata>