X
ML 做爲一個更好的網絡數據表達方式(NDR)
HTTP是一個至關有用的RPC協議,它提供了IIOP或DCOM在組幀、鏈接管理以及序列化對象應用等方面大部分功能的支持。( 並且URLs與IORs和OBJREFs在功能上使人驚歎的接近)。HTTP所缺乏的是用單一的標準格式來表達一個RPC調用中的參數。這則正是
XML的用武之地。
象NDR和CDR,XML是一個與平臺無關的中性的數據表達協議。XML容許數據被序列化成一個能夠傳遞的形式,使得它容易地在任何平臺上被解碼。XML有如下不一樣於NDR和CDR的特色:
有大量XML編碼和解碼軟件存在於每一個編程環境和平臺上XML基於文本,至關容易用低技術水平的編程環境來處理XML是特別靈活的格式,它容易用一致的 方式來被擴展爲支持可擴展性,在XML中每個元素和屬性有一個名域URI與它相聯繫,這個URI用xmlns屬性來指定。
考慮下面的XML文檔:
<reverse_string xmlns="urn:schemas-develop-com:StringProcs">
<string1>Hello, World</string1>
<comment xmlns=‘http://foo.com/documentation‘>
This is a comment!!
</comment>
</reverse_string>
元素<reverse_string>和<string1>的名域URI是urn:schemas-develop-com:StringProcs。 元素<comment>的名域URI是http://foo.com/documentation。第二個URI也是一個URL的事實是不重要的。在這兩 種狀況下,URI簡單地被用來消除元素<reverse_string>,<string1>,<comment>和任何碰巧有一樣標記名的其它元素間的 歧義。
爲了方便,XML容許名域URIs被映射爲局部惟一的前綴。這意味着下面的XML文檔在語義上等同於上面的文檔:
<sp:reverse_string
xmlns:sp="urn:schemas-develop-com:StringProcs"
xmlns:doc=‘http://foo.com/documentation‘
>
<sp:string1>Hello, World</sp:string1>
<doc:comment>
This is a comment!!
</doc:comment>
</sp:reverse_string>
後面的形式對做者來講更容易,尤爲是若是有許多名域URIs在使用時。
XML也支持帶類型的數據表達。正在推出的XML Schema規範爲描述XML數據類型標準化了一個詞聚集。下面是一個元素<reverse_string>的XML Schema的描述:
<schema
xmlns=‘http://www.w3.org/1999/XMLSchema‘
targetNamespace=‘urn:schemas-develop-com:StringProcs‘
>
<element name=‘reverse_string‘>
<type>
<element name=‘string1‘ type=‘string‘ />
<any minOccurs=‘0‘ maxOccurs=‘*‘/>
</type>
</element>
</schema>
這個XML Schema定義闡述了XML名域urn:schemas-develop-com:StringProcs包含了一個名爲 <reverse_string>的元素,這個元素包含了一個名爲string1的子元素(類型爲string),它被0個或更多沒有指定的元素所遵照。
XML Schema 規範還定義了一組內置的原始數據類型和創建一個XML文檔中元素的類型的機制。下面的XML文檔用XML Schema類型屬性來把元素和類型名聯繫在一塊兒:
<customer
xmlns=‘http://customer.is.king.com‘
xmlns:xsd=‘http://www.w3.org/1999/XMLSchema‘
>
<name xsd:type=‘string‘>Don Box</name>
<age xsd:type=‘float‘>23.5</name>
</customer>
鏈接XML文檔事例到XML Schema描述的新的一個機制在本文寫做的時候正在標準化過程當中。
HTTP + XML =
SOAP
SOAP把XML的使用代碼化爲請求和響應參數編碼模式,並用HTTP做傳輸。這彷佛有點抽象。具體地講,一個SOAP方法能夠簡單地看做遵循SOAP 編碼規則的HTTP請求和響應。一個SOAP終端則能夠看做一個基於HTTP的URL,它用來識別方法調用的目標。象CORBA/IIOP同樣,SOAP 不須要具體的對象被綁定到一個給定的終端,而是由具體實現程序來決定怎樣把對象終端標識符映射到
服務器端的對象。
SOAP請求是一個HTTP POST請求。SOAP請求的content-type必須用text/xml。並且它必須包含一個請求-URI。服務器怎樣解釋這個請求-URI是與實 現相關的,可是許多實現中可能用它來映射到一個類或者一個對象。一個SOAP請求也必須用SOAPMethodName HTTP頭來指明將被調用的方法。簡單地講,SOAPMethodName頭是被URI指定範圍的應用相關的方法名,它是用#符做爲分隔符將方法名與 URI分割開:
SOAPMethodName: urn:strings-com:IString#reverse
這個頭代表方法名是reverse,範圍URI是urn:strings-com:Istring。 在SOAP中,規定方法名範圍的名域URI在功能上等同於在DCOM 或 IIOP中規定方法名範圍的接口ID。
簡單的說,一個SOAP請求的HTTP體是一個XML文檔,它包含方法中[in]和[in,out]參數的值。這些值被編碼成爲一個顯著的調用元素的子 元素,這個調用元素具備SOAPMethodName HTTP頭的方法名和名域URI。調用元素必須出如今標準的SOAP <Envelope>和<Body>元素內(後面會更多討論這兩個元素)。下面是一個最簡單的SOAP方法請求:
POST /string_server/Object17 HTTP/1.1
Host: 209.110.197.2
Content-Type: text/xml
Content-Length: 152
SOAPMethodName: urn:strings-com:IString#reverse
<Envelope>
<Body>
<m:reverse xmlns:m=‘urn:strings-com:IString‘>
<theString>Hello, World</theString>
</m:reverse>
</Body>
</Envelope>
SOAPMethodName頭必須與<Body>下的第一個子元素相匹配,不然調用將被拒絕。這容許
防火牆管理員在不解析XML的狀況下有效地過濾對一個具體方法的調用。
SOAP響應的格式相似於請求格式。響應體包含方法的[out]和 [in,out]參數,這個方法被編碼爲一個顯著的響應元素的子元素。這個元素的名字與請求的調用元素的名字相同,但以Response後綴來鏈接。下面是對前面的SOAP請求的SOAP響應:
200 OK
Content-Type: text/xml
Content-Length: 162
<Envelope>
<Body>
<m:reverseResponse xmlns:m=‘urn:strings-com:IString‘>
<result>dlroW ,olleH</result>
</m:reverseResponse>
</Body>
</Envelope>
這裏響應元素被命名爲reverseResponse,它是方法名緊跟Response後綴。要注意的是這裏是沒有SOAPMethodName HTTP頭的。這個頭只在請求消息中須要,在響應消息中並不須要。
讓許多SOAP新手困惑的是SOAP中沒有關於SOAP服務器怎樣使用請求頭來分發請求的要求;這被留爲一個實現上的細節。一些SOAP服務器將映射請 求-URIs到類名,並分派調用到靜態方法或到在請求持續期內存活的類的實例。其它SOAP服務器則將請求-URIs映射到始終存活的對象,常常是用查詢 字符串來編碼一個用來定位在服務器進程中的對象關鍵字。還有一些其它的SOAP服務器用HTTP cookies來編碼一個對象關鍵字,這個關鍵字可被用來在每次方法請求中恢復對象的狀態。重要的是客戶對這些區別並不知道。客戶軟件只是簡單遵循 HTTP和XML的規則來造成SOAP請求,讓服務器自由以它認爲最合適的方式來爲請求服務。
SOAP體的核心
SOAP的XML特性是爲把數據類型的實例序列化成XML的編碼模式。爲了達到這個目的,SOAP不要求使用傳統的RPC風格的代理。而是一個SOAP方法調用包含至少兩個數據類型:請求和響應。考慮這下面個COM IDL代碼:
[ uuid(DEADF00D-BEAD-BEAD-BEAD-BAABAABAABAA) ]
interface IBank : IUnknown {
HRESULT withdraw([in] long account,
[out] float *newBalance,
[in, out] float *amount
[out, retval] VARIANT_BOOL *overdrawn);
}
在任何RPC協議下,account和amount參數的值將出如今請求消息中,newBalance,overdrawn參數的值,還有amount參數的更新值將出如今響應消息中。
SOAP把方法請求和方法響應提高到了一流狀態。在SOAP中,請求和響應實際上類型的實例。爲了理解一個方法好比IBank::withdraw怎樣映射一個SOAP請求和響應類型,考慮下列的數據類型:
struct withdraw {
long account;
float amount;
};
這是一個全部的請求參數被打包成爲一個單一的數據類型。一樣下面的數據表示打包全部響應參數到一個單一的數據類型。
struct withdrawResponse {
float newBalance;
float amount;
VARIANT_BOOL overdrawn;
};
再給出下面的簡單的Visual Basic程序,它使用了之前定義的Ibank接口:
Dim bank as IBank
Dim amount as Single
Dim newBal as Single
Dim overdrawn as Boolean
amount = 100
Set bank = GetObject("soap:http://bofsoap.com/am")
overdrawn = bank.withdraw(3512, amount, newBal)
你可以想象底層的代理(多是一個SOAP,DCOM,或IIOP代理)看上去象圖8中所表示的那樣。這裏,在發送請求消息以前,參數被序列化成爲一個請求對象。一樣被響應消息接收到的響應對象被反序列化爲參數。一個相似的轉變一樣發生在調用的服務器端。
當經過SOAP調用方法時,請求對象和響應對象被序列化成一種已知的格式。每一個SOAP體是一個XML文檔,它具備一個顯著的稱爲<Envelope> 的根元素。標記名<Envelope>由SOAP URI (urn:schemas-xmlsoap-org:soap.v1)來劃定範圍,全部SOAP專用的元素和屬性都是由這個URI來劃定範圍的。SOAP Envelope包含一個可選的<Header>元素,緊跟一個必須的<Body>元素。<Body>元素也有一個顯著的根元素,它或者是一個請求對象或 者是一個響應對象。下面是一個IBank::withdraw請求的編碼:
<soap:Envelope
xmlns:soap=‘urn:schemas-xmlsoap-org:soap.v1‘>
<soap:Body>
<IBank:withdraw xmlns:IBank=
‘urn:uuid:DEADF00D-BEAD-BEAD-BEAD-BAABAABAABAA‘>
<account>3512</account>
<amount>100</amount>
</IBank:withdraw>
</soap:Body>
</soap:Envelope>
下列響應消息被編碼爲:
<soap:Envelope
xmlns:soap=‘urn:schemas-xmlsoap-org:soap.v1‘>
<soap:Body>
<IBank:withdrawResponse xmlns:IBank=
‘urn:uuid:DEADF00D-BEAD-BEAD-BEAD-BAABAABAABAA‘>
<newBalance>0</newBalance>
<amount>5</amount>
<overdrawn>true</overdrawn>
</IBank:withdrawResponse>
</soap:Body>
</soap:Envelope>
注意[in, out]參數出如今兩個消息中。
在檢查了請求和響應對象的格式後,你可能已經注意到序列化格式一般是:
<t:typename xmlns:t=‘namespaceuri‘> ;
<fieldname1>field1value</fieldname1>
<fieldname2>field2value</fieldname2>
</t:typename>
在請求的狀況下,類型是隱式的C風格的結構,它由對應方法中的[in]和[in, out]參數組成。對響應來講,類型也是隱式的C風格的結構,它由對應方法中的[out]和[in, out]參數組成。這種每一個域對應一個子元素的風格有時被稱爲元素正規格式(ENF)。通常狀況下,SOAP只用XML特性來傳達描述包含在元素內容中信 息的註釋。
象DCOM和IIOP同樣,SOAP支持協議頭擴展。SOAP用可選的<Header>元素來傳載被協議擴展所使用的信 息。若是客戶端的SOAP軟件包含要發送頭信息,原始的請求將可能如圖9所示。在這種狀況下命名causality的頭將與請求一塊兒序列化。收到請求後, 服務器端軟件能查看頭的名域URI,並處理它識別出的頭擴展。這個頭擴展被http://comstuff.com URI識別,並期待一個以下的對象:
struct causality {
UUID id;
};
在這種狀況下的請求,若是頭元素的URI不能被識別,頭元素能夠被安全地忽略。
但你不能安全的忽略全部的SOAP體中的頭元素。若是一個特定的SOAP頭對正確處理消息是很關鍵的,這個頭元素能被用SOAP屬性 mustUnderstand=’true’標記爲必須的。這個屬性告訴接收者頭元素必須被識別並被處理以確保正確的使用。爲了強迫前面 causality頭成爲一個必須的頭,消息將被寫成以下形式:
<soap:Envelope
xmlns:soap=‘urn:schemas-xmlsoap-org:soap.v1‘>
<soap:Header>
<causality
soap:mustUnderstand=‘true‘
xmlns="http://comstuff.com">
<id>362099cc-aa46-bae2-5110-99aac9823bff</id>
</causality>
</soap:Header>
<!— soap:Body element elided for clarity —>
</soap:Envelope>
SOAP軟件遇到不能識別必須的頭元素狀況時,必須拒絕這個消息並出示一個錯誤。若是服務器在一個SOAP請求中發現一個不能識別的必須的頭元素,它必 須返回一個錯誤響應而且不發送任何調用到目標對象。若是客戶端在一個SOAP請求中發現一個不能識別出的必須的頭元素,它必須向調用者返回一個運行時錯 誤。(在COM狀況下,這將映射爲一個明顯的HRESULT) .
SOAP數據類型
在SOAP消息中,每一個元素多是一個SOAP結構元素,一個根元素,一個存取元素或一個獨立的元素。在SOAP中,soap:Envelope, soap:Body和 soap:Header 是惟一的三個結構元素。它們的基本關係由下列XML Schema所描述:
<schema targetNamespace=‘urn:schemas-xmlsoap-org:soap.v1‘>
<element name=‘Envelope‘>
<type>
<element name=‘Header‘ type=‘Header‘
minOccurs=‘0‘ />
<element name=‘Body‘ type=‘Body‘
minOccurs=‘1‘ />
</type>
</element>
</schema>
在SOAP元素的四種類型中,除告終構元素外都被用做表達類型的實例或對一個類型實例的引用。
根元素是顯著的元素,它是soap:Body 或是 soap:Header的直接的子元素。其中soap: Body只有一個根元素,它表達調用、響應或錯誤對象。這個根元素必須是soap:Body的第一個子元素,它的標記名和域名URI必須與HTTP SOAPMethodName頭或在錯誤消息狀況下的soap:Fault相對應。而soap:Header元素有多個根元素,與消息相聯繫的每一個頭擴展 對應一個。這些根元素必須是soap:Header的直接子元素,它們的標記名和名域URI表示當前存在擴展數據的類型。
存取元素被用做表達類型的域、屬性或數據成員。一個給定類型的域在它的SOAP表達將只有一個存取元素。存取元素的標記名對應於類型的域名。考慮下列Java 類定義:
package com.bofsoap.IBank;
public class adjustment {
public int account ;
public float amount ;
}
在一個SOAP消息中被序列化的實例以下所示:
<t:adjustment
xmlns:t=‘urn:develop-com:java:com.bofsoap.IBank‘>
<account>3514</account>
<amount>100.0</amount>
</t:adjustment>
在這個例子中,存取元素account和amount被稱着簡單存取元素,由於它們訪問對應於在W3C XML Schema規範 (見 http://www.w3.org/TR/XMLSchema-2) 的Part 2中定義的原始數據類型的值。這個規範指定了字符串,數值,日期等數據類型的名字和表達方式以及使用一個新的模式定義中的<datatype>結構來定義 新的原始類型的機制。
對引用簡單類型的存取元素,元素值被簡單地編碼爲直接在存取元素下的字符數據,如上所示。對引用組合類型的存取 元素(就是那些自身用子存取元素來構造的存取元素),有兩個技術來對存取元素進行編碼。最簡單的方法是把被結構化的值直接嵌入在存取元素下。考慮下面的 Java類定義:
package com.bofsoap.IBank;
public class transfer {
public adjustment from;
public adjustment to;
}
若是用嵌入值編碼存取元素,在SOAP中一個序列化的transfer對象以下所示:
<t:transfer
xmlns:t=‘urn:develop-com:java:com.bofsoap.IBank‘
>
<from>
<account>3514</account>
<amount>-100.0</amount>
</from>
<to>
<account>3518</account>
<amount>100.0</amount>
</to>
</t:transfer>
在這種狀況下,adjustment對象的值被直接編碼在它們的存取元素下。
在考慮組合存取元素時,須要說明幾個問題。先考慮上面的transfer類。類的from和to的域是對象引用,它可能爲空。SOAP用XML Schemas的null屬性來表示空值或引用。下面例子表示一個序列化的transfer對象,它的from域是空的:
<t:transfer
xmlns:t=‘urn:develop-com:java:com.bofsoap.IBank‘
xmlns:xsd=‘http://www.w3.org/1999/XMLSchema/instance‘
>
<from xsd:null=‘true‘ />
<to>
<account>3518</account>
<amount>100.0</amount>
</to>
</t:transfer>
在不存在的狀況下, xsd:null屬性的隱含值是false。給定元素的可否爲空的屬性是由XML Schema定義來控制的。例以下列XML Schema將只容許from存取元素爲空:
<type name=‘transfer‘ >
<element
name=‘from‘
type=‘adjustment‘
nullable=‘true‘
/>
<element
name=‘to‘
type=‘adjustment‘
nullable=‘false‘ <!— false is the default —>
/>
</type>
在一個元素的Schema聲明中若是沒有nullable屬性,就意味着在一個XML文檔中的元素是不能爲空的。Null存取元素的精確格式當前還在修訂中�要了解用更多信息參考最新版本的SOAP規範。
與存取元素相關的另外一個問題是因爲類型關係引發的可代換性。因爲前面的adjustment類不是一個final類型的類,transfer對象的from和to域實際引用繼承類型的實例是可能的。爲了支持這種類型兼容的替換,SOAP使用一個名域限定的類型屬性的XML Schema約定。
這種類型屬性的值是一個對元素具體的類型的限制的名字。考慮下面的adjustment擴展類:
package com.bofsoap.IBank;
public class auditedadjustment extends adjustment {
public int auditlevel;
}
給出下面Java語言:
transfer xfer = new transfer();
xfer.from = new auditedadjustment();
xfer.from.account = 3514; xfer.from.amount = -100;
xfer.from.auditlevel = 3;
xfer.to = new adjustment();
xfer.to.account = 3518; xfer.from.amount = 100;
在SOAP中transfer對象的序列化形式以下所示:
<t:transfer
xmlns:xsd=‘http://www.w3.org/1999/XMLSchema‘
xmlns:t=‘urn:develop-com:java:com.bofsoap.IBank‘
>
<from xsd:type=‘t:auditedadjustment‘ >
<account>3514</account>
<amount>-100.0</amount>
<auditlevel>3</auditlevel >
</from>
<to>
<account>3518</account>
<amount>100.0</amount>
</to>
</t:transfer>
在這裏xsd:type屬性引用一個名域限定的類型名,它能被反序列化程序用於實例化對象的正確類型。由於to存取元素引用到一個被預料的類型的實例(而不是一個可代替的繼承類型),xsd:type屬性是不須要的。
剛纔的transfer類設法迴避了一個關鍵問題。若是正被序列化的transfer對象用下面這種方式初始化將會發生什麼狀況:
transfer xfer = new transfer();
xfer.from = new adjustment();
xfer.from.account = 3514; xfer.from.amount = -100;
xfer.to = xfer.from;
基於之前的議論,在SOAP 中transfer對象的序列化形式以下所示:
<t:transfer
xmlns:t=‘urn:develop-com:java:com.bofsoap.IBank‘>
<from>
<account>3514</account>
<amount>-100.0</amount>
</from>
<to>
<account>3514</account>
<amount>-100.0</amount>
</to>
</t:transfer>
這個表達有兩個問題。首先最容易理解的問題是一樣的信息被髮送了兩次,這致使了一個比實際所須要消息的更大的消息。一個更微妙的可是更重要的問題是因爲 反序列化程序不能分辨兩個帶有一樣值的adjustment對象與在兩個地方被引用的一個單一的adjustment對象的區別,兩個存取元素間的身份關 系就被丟失。若是這個消息接收者已經在結果對象上執行了下面的測試,(xfer.to == xfer.from)將不會返回true。
void processTransfer(transfer xfer) {
if (xfer.to == xfer.from)
handleDoubleAdjustment(xfer.to);
else
handleAdjustments(xfer.to, xfer.from);
}
(xfer.to.equals(xfer.from))可能返回true的事實只是比較了兩個存取元素的值而不是它們身份。
爲 了支持必須保持身份關係的類型的序列化,SOAP支持多引用存取元素。目前咱們接觸到的存取元素是單引用存取元素,也就是說,元素值是嵌入在存取元素下面 的,並且其它存取元素被容許引用那個值(這很相似於在NDR中的[unique]的概念)。多引用存取元素老是被編碼爲只包含已知的soap:href屬 性的空元素。soap:href屬性老是包含一個代碼片斷標識符,它對應於存取元素引用到的實例。若是to和from存取元素已經被編碼爲多引用存取元 素,序列化的transfer對象以下所示:
<t:transfer
xmlns:t=‘urn:develop-com:java:com.bofsoap.IBank‘>
<from soap:href=‘#id1‘ />
<to soap:href=‘#id1‘ />
</t:transfer>
這個編碼假設與adjustment類兼容的一個類型的實例已經在envelope中的其它地方被序列化,並且這個實例已經被用soap:id屬性標記,以下所示:
<t:adjustment soap:id=‘id1‘
xmlns:t=‘urn:develop-com:java:com.bofsoap.IBank‘>
<account>3514</account>
<amount>-100.0</amount>
</t:adjustment>
對多引用存取元素,把代碼段的標識符(例如#id1)分解到正確的實例是反序列化程序的工做。
前面的討論解釋了多引用存取元素怎樣與它的目標實例相關聯。下面要討論的是目標實例在哪裏被序列化。這就關係到獨立元素和包的概念。