亚洲成av人片在线观看,嘿咻视频在线看,国产a精品视频http://www.aygfsteel.com/libin2722/category/27231.htmlLuckyStarzh-cnFri, 09 Nov 2007 16:04:45 GMTFri, 09 Nov 2007 16:04:45 GMT60SOAP協議規范http://www.aygfsteel.com/libin2722/articles/159460.html禮物禮物Fri, 09 Nov 2007 14:11:00 GMThttp://www.aygfsteel.com/libin2722/articles/159460.htmlhttp://www.aygfsteel.com/libin2722/comments/159460.htmlhttp://www.aygfsteel.com/libin2722/articles/159460.html#Feedback0http://www.aygfsteel.com/libin2722/comments/commentRss/159460.htmlhttp://www.aygfsteel.com/libin2722/services/trackbacks/159460.htmlSOAP協議規范

1. 簡介

SOAP以XML形式提供了一個簡單、輕量的用于在分散或分布環境中交換結構化和類型信息的機制。SOAP本身并沒有定義任何應用程序語義,如編程模型或特定語義的實現;實際上它通過提供一個有標準組件的包模型和在模塊中編碼數據的機制,定義了一個簡單的表示應用程序語義的機制。這使SOAP能夠被用于從消息傳遞到RPC的各種系統。

SOAP包括三個部分

  • SOAP封裝(見第4節)結構定義了一個整體框架用來表示消息中包含什么內容,誰來處理這些內容以及這些內容是可選的或是必需的。
  • SOAP編碼規則(見第5節)定義了用以交換應用程序定義的數據類型的實例的一系列機制。
  • SOAP RPC表示(見第7節)定義了一個用來表示遠程過程調用和應答的協定。

雖然這三個部分都作為SOAP的一部分一起描述,但它們在功能上是相交的。特別的,封裝和編碼規則是在不同的名域中定義的,這種模塊性的定義方法增加了簡單性在SOAP封裝,SOAP編碼規則和SOAPRPC協定之外,這個規范還定義了兩個協議的綁定,描述了在有或沒有HTTP擴展框架[6]的情況下,SOAP消息如何包含在HTTP消息[5]中被傳送。

1.1 設計目標

SOAP的主要設計目標是簡單性和可擴展性,這意味著傳統的消息系統和分布對象系統的某些性質不是SOAP規范的一部分。這些性質包括:

  • 分布式碎片收集
  • 成批傳送消息
  • 對象引用(要求分布式碎片收集)
  • 激活機制(要求對象引用)

1.2 符號約定

這篇文章中的關鍵字 "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT","SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", 和"OPTIONAL"的解釋在RFC-2119 [2]中。 這篇文章中用到的名域前綴 "SOAP-ENV" 和"SOAP-ENC"分別與"http://schemas.xmlsoap.org/soap/envelope/" 和"http://schemas.xmlsoap.org/soap/encoding/"關聯。整篇文檔中,名域前綴“xsi”被假定為與URI"http://www.w3.org/1999/XMLSchema-instance“(在XMLSchema規范[11]定義)相連。類似的,名域前綴”xsd“被假定為與URI"http://www.w3.org/1999/XMLSchema"(在[10]中定義)相連。名域前綴”tns“用來表示任意名域。所有其它的名域前綴都只是例子。
名域URI的基本形式”some-URI“表示某些依賴于應用程序或上下文的URI[4]。這個規范用擴展BNF(在RFC-2616[5] 描述)描述某些結構。

1.3 SOAP消息舉例

在這個例子中,GetLastTradePrice SOAP 請求被發往StockQuote服務。這個請求攜帶一個字符串參數和ticker符號,在SOAP應答中返回一個浮點數。XML名域用來區分SOAP標志符和應用程序特定的標志符。這個例子說明了在第6節中定義的HTTP綁定。如果SOAP中管理XML負載的規則完全獨立于HTTP是沒有意義的,因為事實上該負載是由HTTP攜帶的。在Appendix A中有更多的例子。

例1 在HTTP請求中嵌入SOAP消息

POST /StockQuote HTTP/1.1
Host:
www.stockquoteserver.com
Content-Type: text/xml;
charset="utf-8"
Content-Length: nnnn
SOAPAction:
"Some-URI"
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<m:GetLastTradePrice xmlns:m="Some-URI">
<symbol>DIS</symbol>
</m:GetLastTradePrice>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

下面是一條應答消息,包括HTTP消息,SOAP消息是其具體內容 :

例2 在HTTP應答中嵌入SOAP消息

HTTP/1.1 200 OK
Content-Type: text/xml;
charset="utf-8"
Content-Length:
nnnn
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
<SOAP-ENV:Body>
<m:GetLastTradePriceResponse xmlns:m="Some-URI">
<Price>34.5</Price>
</m:GetLastTradePriceResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

2. SOAP消息交換模型

SOAP消息從發送方到接收方是單向傳送,但正如上面顯示的,SOAP消息經常以請求/應答的方式實現。SOAP實現可以通過開發特定網絡系統的特性來優化。例如,HTTP綁定(見第6節)使SOAP應答消息以HTTP應答的方式傳輸,并使用同一個連接返回請求。不管SOAP被綁定到哪個協議,SOAP消息采用所謂的”消息路徑“發送,這使在終節點之外的中間節點可以處理消息。一個接收SOAP消息的SOAP應用程序必須按順序執行以下的動作來處理消息:識別應用程序想要的SOAP消息的所有部分 (見4.2.2節)檢驗應用程序是否支持第一步中識別的消息中所有必需部分并處理它。如果不支持,則丟棄消息(見4.4節)。在不影響處理結果的情況下,處理器可能忽略第一步中識別出的可選部分。如果這個SOAP應用程序不是這個消息的最終目的地,則在轉發消息之前刪除第一步中識別出來的所有部分。為了正確處理一條消息或者消息的一部分,SOAP處理器需要理解:所用的交換方式(單向,請求/應答,多路發送等等),這種方式下接收者的任務,RPC機制(如果有的話)的使用(如第7節中所述),數據的表現方法或編碼,還有其它必需的語義。盡管屬性(比如SOAP encodingstyle,見4.1.1節)可以用于描述一個消息的某些方面,但這個規范并不 強制所有的接收方也必須有同樣的屬性并取同樣的屬性值。舉個例子,某一特定的應用可能知道一個元素表示一條遵循第7節約定的RPC請求,但是另外一些應用可能認為指向該元素的所有消息都用單向傳輸,而不是類似第7節的請求應答模式。
(譯者注:交互雙方的SOAP消息并不一定要遵循同樣的格式設定,而只需要以一種雙方可理解的格式交換信息就可以了)

3. 與XML的關系

所有的SOAP消息都使用XML形式編碼(更多有關XML的信息請見[7])一個SOAP應用程序產生的消息中,所有由SOAP定義的元素和屬性中必須包括正確的名域。SOAP應用程序必須能夠處理它接收到的消息中的SOAP名域(見4.4節),并且它可以處理沒有SOAP名域的SOAP消息,就象它們有正確的名域一樣。SOAP定義了兩個名域(更多有關XML名域的信息請見[8])

  • SOAP封裝的名域標志符是"http://schemas.xmlsoap.org/soap/envelope/"
  • SOAP的編碼規則的名域標志符是"http://schemas.xmlsoap.org/soap/encoding/"

SOAP消息中不能包含文檔類型聲明,也不能包括消息處理指令。[7] SOAP使用"ID"類型"id"屬性來指定一個元素的唯一的標志符,同時該屬性是局部的和無需校驗的。SOAP使用"uri-reference"類型的"href"屬性指定對這個值的引用,同時該屬性是局部的和無需校驗的。這樣就遵從了XML規范[7],XMLSchema規范[11]和XML連接語言規范[9]的風格。除了SOAP mustUnderstand 屬性(見4.2.3節)和SOAPactor屬性(見4.2.2節)之外,一般允許屬性和它們的值出現在XML文檔實例或Schema中(兩者效果相同)。也就是說,在DTD或Schema中聲明一個缺省值或固定值和在XML文檔實例中設置它的值在語義上相同。

4. SOAP封裝

SOAP消息是一個XML文檔,包括一個必需的SOAP封裝,一個可選的SOAP頭和一個必需的SOAP體。在這篇規范剩余部分中,提到SOAP消息時就是指這個XML文檔。這一節中定義的元素和屬性的名域標志符為:

"http://schemas.xmlsoap.org/soap/envelope/" 。一個SOAP消息包括以下部分:1.在表示這個消息的XML文檔中,封裝是頂層元素。2.應用SOAP交換信息的各方是分散的且沒有預先協定,SOAP頭提供了向SOAP消息中添加關于這條SOAP消息的某些要素(feature)的機制。SOAP定義了少量的屬性用來表明這項要素(feature)是否可選以及由誰來處理。(見4.2節)3.SOAP體是包含消息的最終接收者想要的信息的容器(見4.3節)。SOAP為SOAP體定義了一個Fault元素用來報告錯誤信息。語法規則如下所示:

封裝

  1. 元素名是 "Envelope"
  2. 在SOAP消息中必須出現。
  3. 可以包含名域聲明和附加屬性。如果包含附加屬性,這些屬性必須限定名域。類似的,"Envelope"可以包含附加子元素,這些也必須限定名域且跟在SOAP體元素之后。

SOAP頭 (見4.2節)

  1. 元素名是"Header"
  2. 在SOAP消息中可能出現。如果出現的話,必須是SOAP封裝元素的第一個直接子元素。
  3. SOAP頭可以包含多個條目,每個都是SOAP頭元素的直接子元素。所有SOAP頭的直接子元素都必須限定名域。

SOAP體 (見4.3節)

  1. 元素名是"Body"
  2. 在SOAP消息中必須出現且必須是SOAP封裝元素的直接子元素。它必須直接跟在SOAP頭元素(如果有)之后。否則它必須是SOAP封裝元素的第一個直接子元素。
  3. SOAP體可以包括多個條目,每個條目必須是SOAP體元素的直接子元素。SOAP體元素的直接子元素可以限定名域。SOAP定義了SOAPFault元素來表示錯誤信息。

4.1.1 SOAP encodingStyle屬性

EncodingStyle全局屬性用來表示SOAP消息的序列化規則。這個屬性可以在任何元素中出現,作用范圍與名域聲明的作用范圍很相似,為這個元素的內容和它的所有沒有重載此屬性的子元素。SOAP消息沒有定義缺省編碼。屬性值是一個或多個URI的順序列表,每個URI確定了一種或多種序列化規則,用來不同程度反序列化SOAP消息,舉例如下:

"http://schemas.xmlsoap.org/soap/encoding/"
"http://my.host/encoding/restricted http://my.host/encoding/"
""

第5節中定義的序列化規則由URI"http://schemas.xmlsoap.org/soap/encoding/" 確定。使用這個特定序列化規則的消息應該用encodingStyle屬性說明這一點。另外,所有以"http://schemas.xmlsoap.org/soap/encoding/"開頭的URI中的序列化規則與第5節中定義的SOAP編碼規則相一致。一個零長度的URI("")明確顯示所含元素沒有任何編碼形式。這可以用來取消上一級元素的所有編碼聲明。

4.1.2 封裝版本模型

SOAP沒有定義常規的基于主版本號和輔版本號的版本形式。SOAP消息必須有一個封裝元素與名域"http://schemas.xmlsoap.org/soap/envelope/"關聯。如果SOAP應用程序接收到的SOAP消息中的SOAP封裝元素與其他的名域關聯,則視為版本錯誤,應用程序必須丟棄這個消息。如果消息是通過HTTP之類的請求/應答協議收到的,應用程序必須回答一個SOAP VersionMismatch 錯誤信息(見4.4節)。

4.2 SOAP頭

SOAP為相互通信的團體之間提供了一種很靈活的機制:在無須預先協定的情況下,以分散但標準的方式擴展消息。可以在SOAP頭中添加條目實現這種擴展,典型的例子有認證,事務管理,支付等等。頭元素編碼為SOAP封裝元素的第一個直接子元素。頭元素的所有直接子元素稱作條目。條目的編碼規則如下:

一個條目有它的完整的元素名(包括名域URI和局部名)確定。SOAP頭的直接子元素必須有名域限制。
SOAP encodingStyle屬性可以用來指示條目所用的編碼形式(見4.1.1節)
SOAP mustUnderstand屬性(見4.2.3節)和SOAPactor屬性(見4.2.2節)可以用來指示如何處理這個條目以及由誰來處理。(見4.2.1節)

4.2.1 使用頭屬性

這一節中定義的SOAP頭屬性確定了SOAP消息的接收者應該怎樣按第2節中所述的方式處理消息。產生SOAP消息的SOAP應用程序,應該僅僅在SOAP頭元素的直接子元素中使用這些SOAP頭屬性。SOAP消息的接收者必須忽略所有不在SOAP頭元素的直接子元素中SOAP頭屬性。下面的例子是一個SOAP頭,包括一個元素標志符"Transaction","mustUnderstand"取值為"1"和數值5。這應該以如下方式編碼:

<SOAP-ENV:Header>
<t:Transaction
xmlns:t="some-URI" SOAP-ENV:mustUnderstand="1">
5
</t:Transaction>
</SOAP-ENV:Header>

4.2.2 SOAP actor屬性

一個SOAP消息從始節點到終節點的過程中,可能沿著消息路徑經過一系列SOAP中間節點。一個SOAP中間節點是一個可以接收轉發SOAP消息的應用程序。中間節點和終節點由URI區分。可能SOAP消息的終節點并不需要所有部分,而在消息路徑上的一個和幾個中間節點可能需要這些內容。頭元素的接收者扮演的角色類似于一個過濾器,防止這些只發給本接受者的消息部分擴散到其它節點。即一個頭元素的接收者必須不轉發這些頭元素到SOAP消息路徑上的下一個應用程序。同樣的,接收者可能插入一個相似的頭元素。SOAP actor全局屬性可以用于指示頭元素的接收者。SOAP actor屬性的值是一個URI。

URI "http://schemas.xmlsoap.org/soap/actor/next"指出了第一個處理這個消息的SOAP應用程序需要這個頭元素。這類似于HTTP頭中用Connection域表示hop-by-hop范圍模型。省略SOAP actor屬性表示接收者是SOAP消息的終節點。如果這個屬性要生效,它必須出現在SOAP消息實例中。(見第3節和4.2.1節)

4.2.3 SOAP mustUnderstand屬性

SOAP mustUnderstand全局屬性用來指示接受者在處理消息時這個條目是否必須處理。條目的接收者由SOAP actor屬性定義(見4.2.2節)。MustUnderstand屬性的值是"1" 或 "0"。缺少SOAP mustUnderstand屬性在語義上等同于它的值為"0"。如果一個頭元素的SOAP mustUnderstand屬性的值是"1",那么條目的接受者必須或者遵守語義(如以元素的全名傳送)并按照語義正確的處理,或者放棄處理消息(見4.4節)。SOAP mustUnderstand 屬性考慮了消息演變的準確性(robust evolution)。必須假定包含SOAP mustUnderstand屬性且值為"1"的元素以某種方式修改了它們的父元素或同層元素的語義。以這種方式連接元素確保了語義上的變化不會被那些不能完全理解它的接收者忽略。如果這個屬性要生效,它必須出現在SOAP消息實例中。(見第3節和4.2.1節)

4.3 SOAP體

SOAP體元素提供了一個簡單的機制,使消息的最終接收者能交換必要的信息。使用體元素的典型情況包括配置RPC請求和錯誤報告。體元素編碼為SOAP封裝元素的直接子元素。如果已經有一個頭元素,那么體元素必須緊跟在頭元素之后,否則它必須是SOAP封裝元素的第一個直接子元素。體元素的所有直接子元素稱作體條目,每個體條目在SOAP體元素中編碼為一個獨立的元素。條目的編碼規則如下:

  • 一個條目由它的元素全名(包括名域URI和局部名)確定。SOAP體元素的直接子元素可能是名域限制的。
  • SOAP encodingStyle屬性可能用來指示條目(見4.1.1節)的編碼方式。
  • SOAP定義了一個Fault條目用來報告錯誤信息。(見4.4節)

4.3.1 SOAP頭和體的關系

雖然頭和體定義為獨立的元素,它們實際上是有關系的。體條目和頭條目的關系如下:體條目在語義上等同于actor屬性為缺省值且mustUnderstand屬性值為"1"的頭條目。不使用actor屬性則表示缺省的actor。(見4.2.2節)

4.4 SOAP錯誤

SOAP錯誤元素用于在SOAP消息中攜帶錯誤和(或)狀態信息。如果有SOAP錯誤元素,它必須以以體條目的方式出現,并且在一個體元素中最多出現一次。SOAP錯誤元素定義了以下四個子元素:

  • faultcode
    faultcode元素給軟件提供了一個識別此錯誤的算法機制。SOAP錯誤元素必須有faultcode子元素,并且它的值必須是一個合法的名(在[8]節定義)。SOAP定義一些SOAP faultcode描述基本的SOAP錯誤(見4.4.1節)。
  • faultstring
    faultstring元素提供了一個錯誤解釋,而不是為了軟件處理。faultstring元素類似于HTTP中定義(見[5],第6.1節)的'Reason-Phrase'。SOAP錯誤元素必須有faultstring子元素,并且它應該提供一些錯誤本質的解釋信息。
  • faultactor
    faultactor元素提供了在消息路徑上是誰導致了錯誤發生的信息(見第2節)。它類似于SOAP actor屬性(見4.2.2節),只是SOAP actor指的是頭條目的目的地,faultactor指的是錯誤的來源。faultactor屬性的值是用來區分錯誤來源的URI。不是SOAP消息的最終目的地的應用程序必須在SOAP Fault元素中包含faultactor元素。消息的最終目的地可以使用faultactor元素明確的指示是它產生了這個錯誤(參見下面的detail元素)
  • detail
    detail元素用來攜帶與Body元素有關的應用程序所要的錯誤信息。如果Body元素的內容不能被成功的處理,則必須包含detail子元素。它不能用來攜帶屬于頭條目的錯誤信息。頭條目的詳細出錯信息必須由頭條目攜帶。Fault元素中沒有detail元素表示這個錯誤與Body元素的處理無關。在有錯誤的時候,這可以用來區分Body元素有沒有被正確的處理。detail元素的所有直接子元素稱作detail條目,并且每個detail條目在detail元素中編碼為獨立的元素。detail條目的編碼規則如下(參見例10): 一個detail條目由它的元素全名(包括名域URI和局部名)確定。SOAP體元素的直接子元素可能是名域限制的。SOAP encodingStyle屬性可能用來指示detail條目(見4.1.1節)的編碼方式。也可以有其它的Fault子元素,只要它們是名域限制的。

4.4.1 SOAP 錯誤代碼

在描述這個規范中定義的錯誤時,這一節中定義的Faultcode值必須用在faultcode元素中。這些faultcode值得名域標志符為"http://schemas.xmlsoap.org/soap/envelope/"。定義這個規范之外的方法時推薦(不要求)使用這個名域。缺省的SOAP faultcode值以可擴展的方式定義,允許定義新的SOAP faultcode值,并與現有的faultcode值向后兼容。使用的機制類似于HTTP中定義的1xx, 2xx,3xx等基本的狀態類(見[5]第10節),不過,它們定義為XML合法名(見 [8] 第3節 ),而不是整數。 字符"."(點)作為faultcode的分隔符,點左邊的錯誤代碼比右邊的錯誤代碼更為普通。如:

Client.Authentication

這篇文檔中定義的faultcode值是:

名稱 含義
VersionMismatch 處理方發現SOAP封裝元素有不合法的名域(見4.1.2節)
MustUnderstand 處理方不理解或者不服從一個包含值為"1"的
mustUnderstand 屬性的 SOAP頭元素的直接子元素。(見4.2.3節)

Client

Client錯誤類表示消息的格式錯誤或者不包含適當的正確信息。例如,消息可能缺少正確的認證和支付信息。一般地,它表示消息不能不作修改就重發。參見4.4節

SOAP Fault detail子元素的描述。

Server

Server錯誤類表示由于消息的處理過程而不是消息的內容本身使得消息消息不能正確的處理。例如,處理消息時可能要與其它處理器通信,但它沒有響應。這個消息可能在遲一點的時間處理成功。 SOAP Fault子元素的詳細信息參見4.4節

5. SOAP編碼

SOAP編碼格式基于一個簡單的類型系統,概括了程序語言,數據庫和半結構化數據等類型系統的共同特性。一個類型或者是一個簡單的(標量的)類型,或者是由幾個部分組合而成的復合類型,其中每個部分都有自己的類型。以下將詳細描述這些類型。這一節定義了類型化對象的序列化規則。它分兩個層次。首先,給定一個與類型系統的符號系統一致的Schema(譯者注:這里的schema不是符合XML語法的schema,而僅僅表示廣義的用于表示消息結構的定義方式),就構造了XML語法的Schema。然后,給定一個類型系統的Schema和與這個Schema一致的特定的值,就構造了一個XML文檔實例。反之,給定一個依照這些規則產生的XML文檔實例和初始的Schema,就可以構造初始值的一個副本。這一節中定義的元素和屬性的名域標志符為"http://schemas.xmlsoap.org/soap/encoding/"。下面的例子都假定在上一層的元素中聲明了名域。
鼓勵使用這一節中描述的數據模型和編碼方式,但也可以在SOAP中使用其他的數據模型和編碼方式。(見4.1.1節)

5.1 XML中的編碼類型規則

XML允許非常靈活的數據編碼方式。SOAP定義了一個較小的規則集合。這一節在總的層次上定義了這些編碼規則,下一節將描述特定類型的編碼規則的細節。這一節定義的編碼規則可以與第7節中所述的RPC調用和應答映射結合使用。下面的術語用來描述編碼規則:

  • 一個"value"是一個字符串,類型(數字,日期,枚舉等等)的名或是幾個簡單值的組合。所有的值都有特定的類型。
  • 一個"simple value"沒有名部分, 如特定的字符串,整數,枚舉值等等。
  • 一個"compound value"是相關的值的結合,如定單,股票報表,街道地址等等。在"compound value"中,每個相關的值都潛在的以名,序數或這兩者來區分。這叫作"a ccessor"。復合值的例子有定單和股票報表等等。數組也是復合值。在復合值中,多個accessor有相同的名是允許的,例如RDF就是這樣做的。
  • 一個"array"是一個復合值,成員值按照在數組中的位置相互區分。
  • 一個"struct"也是一個復合值,成員值之間的唯一區別是accessor名,accessor名互不相同。
  • 一個"simple type"是簡單值的類,如叫做"string" "integer"的類,還有枚舉類等等。
  • 一個"compound type"是復合值的類。復合類型的例子有定單類,它們有相同的accessor名(shipTo, totalCost等),但可能會有不同的值(可能以后被設置為確定的值)。

在復合類型中,如果類型內的accessor名互不相同,但是可能與其他類型中的accessor名相同,即,accessor名加上類型名形成一個唯一的標志符,這個名叫作"局部范圍名"。如果名是直接或間接的基于URI的一部分,那么不管它出現在什么類型中,這個名本身就可以唯一標志這個accessor,這樣的名叫作"全局范圍名"。給定了schema中相關的值的序列化信息,就可能確定某些值只與某個accessor的一個實例有關。其它情況下則無法確定。當且僅當一個accessor引用一個值,這個值才能被視為"single-reference",如果有不止一個accessor引用它,那么就將它視為"multi-reference"。注意,可能一個確定的值在一個schema中是"single-reference",而在另一個schema中是"multi-reference"。在語句構成上,一個元素可能是"independent" 或 "embedded"。一個獨立的元素指出現在序列化最頂層的任何元素。所有其它元素都是嵌入元素。雖然用xsi:type屬性可以使值的結構和類型變為自描述的,但是序列化規則允許值的類型僅僅參照schema而定。這樣的schema可能使用"XML Schema Part 1: Structures" [10]和"XML Schema Part 2: Datatypes" [11]中描述的符號系統,也可能使用其它符號系統。注意,雖然序列化規則可以用于除了數組和結構之外的復合類型,但是許多schema僅僅包含數組和結構類型。序列化規則如下:

所有的值以元素內容的形式表示。一個multi-reference值必須表示為一個獨立元素的內容,而一個single-reference值最好不要這樣表示(也可以這樣表示)。對于每個具有值的元素,值的類型時必須用下述三種方式之一描述:

  • 所屬元素實例有xsi:type屬性
  • 所屬元素是一個有SOAP-ENC:arrayType 屬性(該屬性可能是缺省的)的元素的子元素,或者
  • 所屬元素的名具有特定的類型,類型可以由schema確定。

一個簡單值表示為字符數據,即沒有任何子元素。每個簡單值必須具有一個類型,這個類型或者是XML Schemas Specification, part 2 [11]有的類型,或者具有源類型(參見5.2節)。一個復合值編碼成一個元素的序列,每個accessor用一個嵌入元素表示,該元素的元素名和accessor的名一致。如果accessor的名是局部于其所屬的類型的,則該元素的元素名不是合格的,否則對應的元素名是合格的。(參見5.4節)
一個multi-reference的簡單值或復合值編碼成一個獨立的元素,這個元素包含一個局部的無需校驗的屬性,屬性名為"id",類型為"ID"(依照XML Specification [7])。值的每個accessor對應一個空元素,該元素有一個局部的,無需校驗的屬性,屬性名為"href",類型為" uri-reference "(依照XML Schema Specification [11]),"href"屬性的值引用了相對應的獨立元素的URI標志符。字符串和字符數組表示為multi-reference的簡單類型,但是特殊的規則使它們在普通的情況下能被更有效的表示(參見5.2.1節和5.2.3節)。字符串和字符數組值的accessor可能有一個名字為"id",類型為"ID"(依照XML Specification [7])的屬性。如果這樣,所有這個值的所有其它accessor編碼成一個空元素,這個元素有一個局部的,無需校驗的屬性,屬性名為"href",類型為" uri-reference "(依照XML Schema Specification [11]),"href"屬性的值引用了包含這個值的元素的URI標志符。編碼時允許一個值有多個引用,就像多個不同的值有多個引用一樣,但這僅在從上下文可以知道這個XML文檔實例的含義沒有改變時才可使用。數組是復合值(參見5.4.2節)。SOAP數組定義為具有類型"SOAP-ENC:Array"或從它衍生的類型.

SOAP數組可以時一維或多維,它們的成員以序數位置相互區分。一個數組值表示為反映這個數組的一系列元素,數組成員按升序出現。對多維數組來說,右邊的這一維變化最快。每個成員元素命名為一個獨立元素。(見規則2)SOAP數組可以是single-reference 或multi-reference值,因此可以表示為嵌入元素或獨立元素的內容。SOAP數組必須包含一個"SOAP-ENC:arrayType"屬性,它的值指定了包含元素的類型和數組的維數。"SOAP-ENC:arrayType"屬性的值定義如下:

arrayTypeValue = atype asize
atype = QName *( rank )
rank = "[" *( "," ) "]"
asize = "[" #length "]"
length = 1*DIGIT

  • "atype"結構是被包含元素的類型名,它表示為QName并且作為類型限制在XML元素聲明的
  • "type"屬性中出現(這意味著被包含元素的所有值都要與該類型一致,即在SOAP-ENC:a rrayType中引用的類型必須是每個數組成員的類型或超類型)。在arrays of arrays or "jagged arrays"的情況下,類型組件編碼為"innermost"類型且在從第一層開始的嵌套數組的每一層中,類型名后都跟隨一個rank結構。多維數組編碼時從第一維起,每一維之間用逗號隔開。
  • "asize"結構包含一個以逗號分隔的列表,數值0,1或其它整數表示數組每一維的長度。整數0表示沒有指定詳細的大小,但是可能在檢查數組實際成員的大小后確定。例如,一個5個成員的整型數組的arrayTypeValue值為"int[][5]",它的atype值是int[]",asize值是"[5]"。同樣,一個3個成員的兩維整型數組的arrayTypeValue值為"int[,][3]",它的atype值是int[,]",asize值是"[3]"。

一個SOAP數組成員可能包含一個"SOAP-ENC:offset"屬性表示這一項在整個數組中的位置偏移值。這被用來指示一個部分儲值數組(見5.4.2.1節)的位置偏移值。同樣,一個數組成員可能包含一個"SOAP-ENC:position"屬性表示這一項在整個數組中的位置,這被用來描述稀疏數組(見5.4.2.2節)的成員。"SOAP-ENC:offset" 和"SOAP-ENC:position"屬性值的定義如下:

arrayPoint = "[" #length "]"
偏移值和位置從0開始
NULL值或缺省值可能通過省略accssor元素來表示。NULL值也可能通過一個包含值為'1'的xsi:null屬性的accssor元素來表示,其它的依賴于應用程序的屬性和值也可能用來表示NULL值。注意,規則2允許獨立的元素和數組成員名不同于值類型的元素。

5.2 簡單類型

SOAP采用了"XML Schema Part 2: Datatypes"規范[11]"Built-in datatypes"節中的所有類型作為簡單類型,包括值和取值范圍。例如:

類型 舉例
int 58502
float 314159265358979E+1
negativeInteger -32768
string Louis "Satchmo" Armstrong

在XML Schema規范中聲明的數據類型可以直接用在元素schema中,也可以使用從這些類型衍生的新類型。一個schema和對應的具有這些類型的元素的數據實例的例子如下所示:

<element name="age" type="int"/>
<element name="height" type="float"/>
<element name="displacement" type="negativeInteger"/>
<element name="color">
<simpleType base="xsd:string">
<enumeration value="Green"/>
<enumeration value="Blue"/>
</simpleType>
</element>
<age>45</age>
<height>5.9</height>
<displacement>-450</displacement>
<color>Blue</color>

所有簡單值必須編碼為元素的內容,它的類型或者在"XML Schema Part 2: Datatypes"規范[11]中定義過,或者是基于一個用XML Schema規范提供的機制能推衍生出的類型。如果一個簡單值編碼為獨立元素或異質數組成員,那么有一個對應于數據類型的元素聲明將會很方便。因為"XML Schema Part 2: Datatypes"規范[11]包括了類型定義,但是不包括對應的元素聲明,SOAP-ENC schema和名域為每個簡單數據類型聲明了一個元素,如<SOAP-ENC:int id="int1">45</SOAP-ENC:int>

5.2.1 字符串

字符串數據類型的定義在"XML Schema Part 2: Datatypes"規范[11]中。注意,這不同于許多數據庫和程序語言中的"string"類型,特別的,字符串數據類型可能禁止某些在那些語言中允許的字符。(這些值必須用xsd:string之外的數據類型表示)一個字符串可能編碼為一個single-reference 或 multi-reference值。包含字符串值的元素可能有一個"id"屬性。附加的accessor元素可能有對應的"href"屬性。
例如,同一字符串的兩個accessor可能以如下形式出現:

<greeting id="String-0">Hello</greeting>
<salutation href="#String-0"/>

但是,如果兩個accessor參考同一字符串實例(或字符串的子類型),這不是一個實質問題,它們可以編碼為兩個single-reference值,如下所示:

<greeting>Hello</greeting>
<salutation>Hello</salutation>

這個例子的schema片斷如下所示:

<element name="greeting" type="SOAP-ENC:string"/>
<element name="salutation" type="SOAP-ENC:string"/>

在這個例子中,SOAP-ENC:string類型用作元素的類型,這是聲明數據類型是"xsd:string"且允許"id" 和"href"屬性的元素的簡便方法。精確定義參見SOAP編碼schema。Schemas可以使用這些源自SOAP編碼schema的聲明,但也可以不這樣做。

5.2.2 Enumerations

"XML Schema Part 2: Datatypes"規范 [11] 定義了"enumeration."機制。SOAP數據模型直接采用了這種機制。但是,由于程序語言和其它語言在定義枚舉時通常有些不同,所以我們在這里詳細闡述了它的概念并描述了一個列表成員的可能取的值是如何編碼的。"Enumeration"作為一個概念表示不同的名字的集合。一個特定的枚舉就是對應于特定的基類型的不同的值的列表。例如,顏色集合("Green", "Blue", "Brown")可以定義為基于字符串類型的枚舉,("1", "3", "5")可能是一個基于整型數的枚舉,等等。"XML Schema Part 2: Datatypes" [11]支持除了布爾型以外所有簡單類型的枚舉。"XML Schema Part 1: Structures"規范[10]的語言可以用來定義枚舉類型。如果schema由另一個沒有特定基類型適用的符號系統生成,就使用"string"。在下面schema的例子中,"EyeColor"定義為字符串,可能的值是"Green", "Blue", 或"Brown"的枚舉,數據實例按照schema顯示如下。

<element name="EyeColor" type="tns:EyeColor"/>
<simpleType name="EyeColor" base="xsd:string">
<enumeration value="Green"/>
<enumeration value="Blue"/>
<enumeration value="Brown"/>
</simpleType>
<Person>
<Name>Henry Ford</Name>
<Age>32</Age>
<EyeColor>Brown</EyeColor>
</Person>

5.2.3 字符數組

一個字符數組可能編碼為single-reference 或multi-reference值。字符數組的編碼規則與字符串的編碼規則類似。特別的,包含字符數組的元素值可能由一個"id"屬性,附加的accssor元素可能有相應的"href"屬性。推薦使用定義在XML Schemas [10][11]中的'base64'編碼(使用在2045 [13]中定義的base64編碼算法)表示模糊字符數組。不過,由于行長度(line length)的限制,通常在MIME中應用base64編碼,SOAP中一般不應用base64編碼。但是提供了"SOAP-ENC:base64"子類型使之能用于SOAP。

<picture xsi:type="SOAP-ENC:base64">
aG93IG5vDyBicm73biBjb3cNCg==
</picture>

5.3 多態accessor

許多語言允許能夠多態訪問多種類型值的accessor,每種類型在運行時可用。一個多態accessor實例必須包含一個"xsi:type"屬性描述實際值的類型。例如,一個名為"cost"類型值為"xsd:float"的多態accessor編碼如下:

<cost xsi:type="xsd:float">29.95</cost>與之對比,類型值不變的accessor編碼如下:

<cost>29.95</cost>

5.4 Compound types復合類型

SOAP定義了與下列常在程序語言中出現的結構性模式對應的類型:

  • 結構:一個"struct"是一個復合值,它的成員值的唯一區別是accessor名稱,任意兩個accessor名稱都不相同。
  • 數組:一個"array"是一個復合值,它的成員值的唯一區別是序數位置。

SOAP也允許結構和數組之外的其它數據的序列化,例如Directed-Labeled-Graph Data Model之類的數據中,單個節點有許多不同的accssor,有些不止出現一次。SOAP序列化規則不要求底層的數據模型在accssor之間區分次序,但如果有這樣的次序的話,這些accssor必須按照這個順序編碼。

5.4.1 復合值,結構和值引用

復合值的成員編碼為accessor元素。當accessor由名區分時(如結構),accessor名即作為元素名。名局部于類型的accessor有不受限的名,其它的accessor則有受限的名。下面的例子是類型為"Book"的結構:

<e:Book>
<author>Henry Ford</author>
<preface>Prefatory text</preface>
<intro>This is a book.</intro>
</e:Book>

以下是描述上面結構的schema片斷:

<element name="Book">
<complexType>
<element name="author" type="xsd:string"/>
<element name="preface" type="xsd:string"/>
<element name="intro" type="xsd:string"/>
</complexType>
</e:Book>

以下是一個同時具有簡單和復雜成員類型的例子。它顯示兩層引用。注意"Author"accssor元素的"href"屬性是對相應具有"id"屬性的值的引用。"Address"與之類似。

<e:Book>
<title>My Life and Work</title>
<author href="#Person-1"/>
</e:Book>
<e:Person id="Person-1">
<name>Henry Ford</name>
<address href="#Address-2"/>
</e:Person>
<e:Address id="Address-2">
<email>mailto:henryford@hotmail.com</email>
<web>http://www.henryford.com</web>
</e:Address>

當"Person"的值和"Address"的值是multi-reference時,上面的形式是正確的。如果它
們是single-reference,就必須用嵌入的形式,如下所示:

<e:Book>
<title>My Life and Work</title>
<author>
<name>Henry Ford</name>
<address>
<email>mailto:henryford@hotmail.com</email>
<web>http://www.henryford.com</web>
</address>
</author>
</e:Book>

如果添加一個限制,任意兩個人都不會有相同的地址,并且地址可以是街道或Email地址,一本書可以有兩個作者,編碼如下:

<e:Book>
<title>My Life and Work</title>
<firstauthor href="#Person-1"/>
<secondauthor href="#Person-2"/>
</e:Book>
<e:Person id="Person-1">
<name>Henry Ford</name>
<address xsi:type="m:Electronic-address">
<email>mailto:henryford@hotmail.com</email>
<web>http://www.henryford.com</web>
</address>
</e:Person>
<e:Person id="Person-2">
<name>Samuel Crowther</name>
<address xsi:type="n:Street-address">
<street>Martin Luther King Rd</street>
<city>Raleigh</city>
<state>North Carolina</state>
</address>
</e:Person>

序列化可以包含對不在同一個資源的值的引用:

<e:Book>
<title>Paradise Lost</title>
<firstauthor />
</e:Book>

以下是描述上面結構的schema片斷:

<element name="Book" type="tns:Book"/>
<complexType name="Book">
<!-- Either the following group must occur or else the
href attribute must appear, but not both. -->
<sequence minOccurs="0" maxOccurs="1">
<element name="title" type="xsd:string"/>
<element name="firstauthor" type="tns:Person"/>
<element name="secondauthor" type="tns:Person"/>
</sequence>
<attribute name="href" type="uriReference"/>
<attribute name="id" type="ID"/>
<anyAttribute namespace="##other"/>
</complexType>
<element name="Person" base="tns:Person"/>
<complexType name="Person">
<!-- Either the following group must occur or else the
href attribute must appear, but not both. -->
<sequence minOccurs="0" maxOccurs="1">
<element name="name" type="xsd:string"/>
<element name="address" type="tns:Address"/>
</sequence>
<attribute name="href" type="uriReference"/>
<attribute name="id" type="ID"/>
<anyAttribute namespace="##other"/>
</complexType>
<element name="Address" base="tns:Address"/>
<complexType name="Address">
<!-- Either the following group must occur or else the
href attribute must appear, but not both. -->
<sequence minOccurs="0" maxOccurs="1">
<element name="street" type="xsd:string"/>
<element name="city" type="xsd:string"/>
<element name="state" type="xsd:string"/>
</sequence>
<attribute name="href" type="uriReference"/>
<attribute name="id" type="ID"/>
<anyAttribute namespace="##other"/>
</complexType>

5.4.2 數組

SOAP數組定義為具有"SOAP-ENC:Array"類型或一個從"SOAP-ENC:Array"衍生的類型(參見規則8)。數組表示為元素值,對元素的名沒有特別的約束(正如元素值并不約束它們所屬的元素)。數組可以包含任意類型的元素,包括嵌套數組。可以創建新的類型(受SOAP-ENC:Array
類型限制)來表示數組,如整數數組或某些用戶定義的枚舉。數組值表示為組成這個數組的項的元素的規則序列。在數組值中,元素名對于區分accesor并不重要。元素可以有任意的名。實際上,元素常常用它們在schema中暗示或確定的數組類型來命名元素。并且一般情況下對于復合值來說,如果數組中數組項的值是single-reference值,則這個數組項包含它的值,否則,該數組項通過"href"屬性引用這個值。下面的例子是一個整型數組的schema片斷:

<element name="myFavoriteNumbers"
type="SOAP-ENC:Array"/>
<myFavoriteNumbers
SOAP-ENC:arrayType="xsd:int[2]">
<number>3</number>
<number>4</number>
</myFavoriteNumbers>

在這個例子中,數組"myFavoriteNumbers"包括幾個成員,每個成員是一個類型為SOAP-ENC:int的值。注意SOAP-ENC:Array允許不受限制的元素名,它們不傳達任何類型信息,所以在使用時,或者它們有xsi:type屬性,或者它們所屬的元素有SOAP-ENC:arrayType屬性。自然,由SOAP-ENC:Array衍生的類型可以聲明局部元素,但這種情況下要包括類型信息。上面已經提到,SOAP-ENC schema包含了元素的聲明,元素名與"XML Schema Part 2: Datatypes"規范[11]中的簡單類型一致。其中包括了對"Array"的聲明。于是,我們可以這樣寫:

<SOAP-ENC:Array SOAP-ENC:arrayType="xsd:int[2]">
<SOAP-ENC:int>3</SOAP-ENC:int>
<SOAP-ENC:int>4</SOAP-ENC:int>
</SOAP-ENC:Array>

數組可以包含特定arrayType的任意子類型的實例。即,數組成員可以是arryType屬性值指定的類型的任意子類型,這個類型對于arrayType屬性中指定的類型來說是可替換的(根據schema中的替換規則)。例如,一個整型數組可以包含從整型衍生的任意類型(如"int"或任意用戶定義的從整型衍生的類型)。同樣,一個"address"數組可能包含一個address的受限類型或擴展類型如"internationalAddress"。因為提供的SOAP-ENC:Array類型允許任意類型的成員,所以可以包含任意類型的混合除非使用arrayType屬性加以特別的限制。在實例中,可以使用xsi:type指定成員元素的類型,或通過schema中成員元素的聲明來指定。下面是兩個例子。

<SOAP-ENC:Array SOAP-ENC:arrayType="SOAP-ENC:ur-type[4]">
<thing xsi:type="xsd:int">12345</thing>
<thing xsi:type="xsd:decimal">6.789</thing>
<thing xsi:type="xsd:string">
Of Mans First Disobedience, and the Fruit
Of that Forbidden Tree, whose mortal tast
Brought Death into the World, and all our woe,
</thing>
<thing xsi:type="xsd:uriReference"> http://www.dartmouth.edu/~milton/reading_room/ </thing>
</SOAP-ENC:Array>
<SOAP-ENC:Array SOAP-ENC:arrayType="SOAP-ENC:ur-type[4]">
<SOAP-ENC:int>12345</SOAP-ENC:int>
<SOAP-ENC:decimal>6.789</SOAP-ENC:decimal>
<xsd:string>
Of Mans First Disobedience, and the Fruit
Of that Forbidden Tree, whose mortal tast
Brought Death into the World, and all our woe,
</xsd:string>
<SOAP-ENC:uriReference> http://www.dartmouth.edu/~milton/reading_room/ </SOAP-ENC:uriReference >
</SOAP-ENC:Array>

數組值可以是結構或其它復合值。例如"xyz:Order"結構數組:

<SOAP-ENC:Array SOAP-ENC:arrayType="xyz:Order[2]">
<Order>
<Product>Apple</Product>
<Price>1.56</Price>
</Order>
<Order>
<Product>Peach</Product>
<Price>1.48</Price>
</Order>
</SOAP-ENC:Array>

數組成員值也可以是數組。下例是兩個字符串數組組成的數組:

<SOAP-ENC:Array SOAP-ENC:arrayType="xsd:string[][2]">
<item href="#array-1"/>
<item href="#array-2"/>
</SOAP-ENC:Array>
<SOAP-ENC:Array id="array-1" SOAP-ENC:arrayType="xsd:string[2]">
<item>r1c1</item>
<item>r1c2</item>
<item>r1c3</item>
</SOAP-ENC:Array>
<SOAP-ENC:Array id="array-2" SOAP-ENC:arrayType="xsd:string[2]">
<item>r2c1</item>
<item>r2c2</item>
</SOAP-ENC:Array>

包含數組的元素無需命名為"SOAP-ENC:Array"。它可以有任意的名,只要元素的類型是SOAP-ENC:Array或由之衍生的類型。例如,下面是一個schema片斷和與之一致的數組實例。

<simpleType name="phoneNumber" base="string"/>
<element name="ArrayOfPhoneNumbers">
<complexType base="SOAP-ENC:Array">
<element name="phoneNumber" type="tns:phoneNumber" maxOccurs="unbounded" />
</complexType>
<anyAttribute/>
</element>
<xyz:ArrayOfPhoneNumbers SOAP-ENC:arrayType="xyz:phoneNumber[2]">
<phoneNumber>206-555-1212</phoneNumber>
<phoneNumber>1-888-123-4567</phoneNumber>
</xyz:ArrayOfPhoneNumbers>

數組可能是多維的。在這種情況下,在arrayType屬性的asize部分將不止有一個值:

<SOAP-ENC:Array SOAP-ENC:arrayType="xsd:string[2,3]">
<item>r1c1</item>
<item>r1c2</item>
<item>r1c3</item>
<item>r2c1</item>
<item>r2c2</item>
<item>r2c3</item>
</SOAP-ENC:Array>

雖然上面的例子把數組編碼為獨立的元素,但元素值也可以是嵌入形式,而且若元素值是single reference時,必須編碼為嵌入形式。下例是一個schema片斷,電話號碼數組嵌入到一個類型為"Person"的結構中,并且通過accessor "phone-numbers"訪問它:

<simpleType name="phoneNumber" base="string"/>
<element name="ArrayOfPhoneNumbers">
<complexType base="SOAP-ENC:Array">
<element name="phoneNumber" type="tns:phoneNumber" maxOccurs="unbounded"/>
</complexType>
<anyAttribute/>
</element>
<element name="Person">
<complexType>
<element name="name" type="string"/>
<element name="phoneNumbers" type="tns:ArrayOfPhoneNumbers"/>
</complexType>
</element>
<xyz:Person>
<name>John Hancock</name>
<phoneNumbers SOAP-ENC:arrayType="xyz:phoneNumber[2]">
<phoneNumber>206-555-1212</phoneNumber>
<phoneNumber>1-888-123-4567</phoneNumber>
</phoneNumbers>
</xyz:Person>

下面的例子中,數組值為single-reference,被編碼為嵌入元素,包含它的元素名即為入口名:

<xyz:PurchaseOrder>
<CustomerName>Henry Ford</CustomerName>
<ShipTo>
<Street>5th Ave</Street>
<City>New York</City>
<State>NY</State>
<Zip>10010</Zip>
</ShipTo>
<PurchaseLineItems SOAP-ENC:arrayType="Order[2]">
<Order>
<Product>Apple</Product>
<Price>1.56</Price>
</Order>
<Order>
<Product>Peach</Product>
<Price>1.48</Price>
</Order>
</PurchaseLineItems>
</xyz:PurchaseOrder>

5.4.2.1 部分儲值(partially transmitted)數組

SOAP提供了對部分儲值(partially transmitted)數組的支持,如某些上下文中的可變數組。一個partially transmitted 數組由一個"SOAP-ENC:offset"屬性(從第一個transmitted的元素開始的偏移量,基于0)指示。如果省略,偏移量取0。下面的例子中數組的大小為5,但只有從0起,第三和第四個元素被儲值。

<SOAP-ENC:Array ;SOAP-ENC:arrayType="xsd:string[5]" ;SOAP-ENC:offset="[2]">
<item>The third element</item>
<item>The fourth element</item>
</SOAP-ENC:Array>

5.4.2.2 稀疏數組Sparse Arrays

SOAP提供了對稀疏數組的支持。每個表示成員值的元素包含一個"SOAP-ENC:position"屬性,用來指示它在數組中的位置。下例是兩維字符串稀疏數組的例子,數組大小是4,但只用到第2個。

<SOAP-ENC:Array SOAP-ENC:arrayType="xsd:string[,][4]">
<SOAP-ENC:Array href="#array-1" SOAP-ENC:position="[2]"/>
</SOAP-ENC:Array>
<SOAP-ENC:Array id="array-1" SOAP-ENC:arrayType="xsd:string[10,10]">
<item SOAP-ENC:position="[2,2]">Third row, third col</item>
<item SOAP-ENC:position="[7,2]">Eighth row, third col</item>
</SOAP-ENC:Array>

如果對array-1的引用僅發生在數組內部,上例也可以編碼如下:

<SOAP-ENC:Array SOAP-ENC:arrayType="xsd:string[,][4]">
<SOAP-ENC:Array SOAP-ENC:position="[2]" SOAP-ENC:arrayType="xsd:string[10, 10]>
<item SOAP-ENC:position="[2,2]">Third row, third col</item>
<item SOAP-ENC:position="[7,2]">Eighth row, third col</item>
</SOAP-ENC:Array>
</SOAP-ENC:Array>

5.4.3 一般復合類型

在這里提到的編碼規則不僅僅限于accessor名已知的情況,如果accessor名是運行環境下實時獲得的,編碼規則同樣適用,也就是說accessor編碼成一個元素名與accessor名匹配的元素,同時accessor可能包含或者引用該元素的值。如果accessor包含類型不能事先確定的值,它必須包含一個合適的屬性xsi:type 。類似地,上述引用的規則已經足夠用于復合類型的序列化,這些復合類型可能包含用名區分的accessors(結構)和用名及序數位置區分的accessors。(可能包含重復的accessor) 實際上這并不要求任何schema模式包含這些類型,但更為準確的說法是:一個類型模型(type-model)schema如果有這些類型,就可以構造一個符合XML句法規則的schema和XML文檔實例。

<xyz:PurchaseOrder>
<CustomerName>Henry Ford</CustomerName>
<ShipTo>
<Street>5th Ave</Street>
<City>New York</City>
<State>NY</State>
<Zip>10010</Zip>
</ShipTo>
<PurchaseLineItems>
<Order>
<Product>Apple</Product>
<Price>1.56</Price>
</Order>
<Order>
<Product>Peach</Product>
<Price>1.48</Price>
</Order>
</PurchaseLineItems>
</xyz:PurchaseOrder>

類似地,將一個結構上類似數組但實際上不是一個 SOAP-ENC:Array類型或者 SOAP-ENC:Array子類型的復合值序列化同樣是允許的,例如:

<PurchaseLineItems>
<Order>
<Product>Apple</Product>
<Price>1.56</Price>
</Order>
<Order>
<Product>Peach</Product>
<Price>1.48</Price>
</Order>
</PurchaseLineItems>

5.5 缺省值

省略accessor元素意味著或者有一個缺省值或者值不知道。具體細節依靠這個accessor,方法和上下文。例如,對于多態accessor,省略accessor一般意味著一個Null值。同樣,省略布爾accessor一般意味著False值或者值不知道,省略數字accessor一般意味著值為零或者值不知道。

5.6 SOAP root屬性

SOAP root 屬性可用于標記一個序列化root,從而一個對象可以反序列化(deserialized),而實際上該root并不是真正的對象root。這個屬性有兩個可選值"1" or "0"。對象真正的roots屬性值為“1” ,序列化root但不是真正的root屬性值也為“1”,元素如果要顯式地指定不能為序列化root,只需將該屬性設置為“0” SOAP root屬性可以出現在SOAP頭和SOAP體元素的任意子元素中。(譯者注:SOAP root屬性為0的元素不是一個獨立的實體,外部的應用不能訪問到該元素,但該元素可以被SOAP文檔本身的其它元素訪問到)SOAP root屬性可以出現在SOAP頭和SOAP體元素的任意子元素中。這個屬性沒有缺省值。

6. 在HTTP中使用SOAP

這一節講述了如何在HTTP中使用SOAP。把SOAP綁定到HTTP,無論使用或不用HTTP擴展框架,都有很大的好處:在利用SOAP的形式化和靈活性的同時,使用HTTP種種豐富的特性。在HTTP中攜帶SOAP消息,并不意味著SOAP改寫了HTTP已有的語義,而是將構建在HTTP之上SOAP語義自然地對應到HTTP語義。SOAP自然地遵循HTTP的請求/應答消息模型使得SOAP的請求和應答參數可以包含在HTTP請求和應答中。注意,SOAP的中間節點與HTTP的中間節點并不等同,即,不要期望一個根據HTTP連接頭中的域尋址到的HTTP中間節點能夠檢查或處理HTTP請求中的SOAP消息。
在HTTP消息中包含SOAP實體時,按照RFC2376[3] HTTP應用程序必須使用媒體類型 "text/xml"。

6.1 SOAP HTTP請求

雖然SOAP可能與各種HTTP請求方式相結合,但是綁定僅定義了在HTTP POST請求中包含SOAP消息。(第7節中描述了如何在RPC中使用SOAP,第6.3節描述了如何使用HTTP擴展框架)

6.1.1 HTTP頭中SOAPAction域

一個HTTP請求頭中的SOAPAction域用來指出這是一個SOAP HTTP請求,它的值是所要的URI。在格式、URI的特性和可解析性上沒有任何限制。當HTTP客戶發出SOAP HTTP請求時必須使用在HTTP頭中使用這個域。

soapaction = "SOAPAction" ":" [ <"> URI-reference <"> ]
URI-reference = <as defined in RFC 2396 [4]>

HTTP頭中SOAPAction域使服務器(如防火墻)能正確的過濾HTTP中SOAP請求消息。如果這個域的值是空字符串(""),表示SOAP消息的目標就是HTTP請求的URI。這個域沒有值表示沒有SOAP消息的目標的信息。例子:

SOAPAction: "http://electrocommerce.org/abc#MyMessage"
SOAPAction: "myapp.sdl"
SOAPAction: ""
SOAPAction:

6.2 SOAP HTTP應答

SOAP HTTP遵循HTTP 中表示通信狀態信息的HTTP狀態碼的語義。例如,2xx狀態碼表示這個包含了SOAP組件的客戶請求已經被成功的收到,理解和接受。在處理請求時如果發生錯誤,SOAP HTTP服務器必須發出應答HTTP 500 "Internal Server Error",并在這個應答中包含一個SOAP Fault元素(見4.4節)表示這個SOAP處理錯誤。

6.3 HTTP擴展框架

一個SOAP消息可以與HTTP擴展框架 [6]一起使用以區分是否有SOAP HTTP請求和它的目標。是使用擴展框架或是普通的HTTP關系到通信各方的策略和能力。通過使用一個必需的擴展聲明和"M-"HTTP方法名前綴,客戶可以強制使用HTTP擴展框架。服務器可以使用HTTP狀態碼510 "Not Extended"強制使用HTTP擴展框架。也就是說,使用一個額外的來回,任何一方都可以發現另一方的策略并依照執行。用來表示SOAP使用了擴展框架的擴展標志符是:http://schemas.xmlsoap.org/soap/envelope/

6.4 SOAP HTTP舉例

例3 使用POST的SOAP HTTP

POST /StockQuote HTTP/1.1
Content-Type: text/xml; charset="utf-8"
Content-Length: nnnn
SOAPAction: "http://electrocommerce.org/abc#MyMessage"
<SOAP-ENV:Envelope...
HTTP/1.1 200 OK
Content-Type: text/xml; charset="utf-8"
Content-Length: nnnn
<SOAP-ENV:Envelope...

例4 使用擴展框架的SOAP HTTP

M-POST /StockQuote HTTP/1.1
Man: "http://schemas.xmlsoap.org/soap/envelope/"; ns=NNNN
Content-Type: text/xml; charset="utf-8"
Content-Length: nnnn
NNNN-SOAPAction: "http://electrocommerce.org/abc#MyMessage"
<SOAP-ENV:Envelope...
HTTP/1.1 200 OK

Ext:
Content-Type: text/xml; charset="utf-8"
Content-Length: nnnn
<SOAP-ENV:Envelope...

7. 在RPC中使用SOAP

設計SOAP的目的之一就是利用XML的擴展性和靈活性來封裝和交換RPC調用。這一節定義了遠程過程調用和應答的統一表示形式。雖然可以預計到這種表示形式最可能被用于與第5節中定義的編碼方式相結合,但也可能有其它的表示形式。SOAP的encodingStyle屬性(見4.3.2節)可以用來表明方法調用和應答都使用這一節所指定的表示方式。在RPC中使用SOAP和SOAP協議綁定(見第6節)是緊密相關的。在使用HTTP作為綁定協議時,一個RPC調用自然地映射到一個HTTP請求,RPC應答同樣映射到HTTP應答。但是,在RPC中使用SOAP并不限于綁定HTTP協議。
要進行方法調用,以下的信息是必需的:

  • 目標對象的URI
  • 方法名
  • 方法signature(可選)
  • 方法的參數
  • 頭數據(可選)

SOAP依靠協議綁定提供傳送URI的機制。例如,對HTTP來說,請求的URI指出了調用的來源 。除了必須是一個合法的URI之外,SOAP對一個地址的格式沒有任何限制。(更多URI的信息參見 [4])

7.1 RPC和SOAP體

RPC方法調用和應答都包含在SOAP Body元素中(見4.3節),它們使用如下的表示形式:

  • 一個方法調用用一個結構表示
  • 一個方法調用被看作一個單個的結構,每個[in]和[in/out]參數有一個accessor。結構的名和類型與方法相同。每個[in]和[in/out]參數都被看作一個accessor,這個accessor的名和類型與參數的名和類型相對應。它們的出現順序和方法中定義的參數順序相同。
  • 一個方法應答用一個結構表示。
  • 一個方法應答被看作一個單個的結構,返回值和每個[in]和[in/out]參數有一個accessor。第一個accessor是返回值,之后是參數accessor,參數accessor的出現順序和方法中定義的參數順序相同。每個參數accessor的名稱和類型與參數的名稱和類型相對應。返回值accessor的名稱并不重要。同樣,結構的名稱也不重要,不過,通常在方法名稱的后面加上字符串"Response"作為結構的名稱。

方法錯誤使用SOAP Fault元素(見4.4節)表示。如果綁定的協議有額外的規則表示錯誤,則這些規則也必須要遵從。正如上面所述,方法調用和應答結構可以按照第5節中規則編碼,或者用encodingStyle屬性(見4.1.1節)指定編碼方式。應用程序可以處理缺少參數的請求,但是可能返回一個錯誤。因為返回結果表示調用成功,錯誤表示調用失敗,所以,在方法應答中同時包含返回結果和錯誤是錯誤的。

7.2 RPC和SOAP頭

在RPC編碼中,可能會有與方法請求有關但不是正規的方法signature的附加信息。如果這樣,它必須作為SOAP頭元素的子元素。使用這種頭元素的一個例子是在消息中傳遞事務ID。由于事務ID不是方法signature的一部分,通常由底層的組件而不是應用程序代碼控制,所以沒有一種直接的方法在調用中傳遞這個必要的信息。通過在頭中添加一個給定名字的條目,接收方的事務管理器就可以析取這個事務ID,而且不影響遠程過程調用的代碼。

8. 安全性考慮

這篇文檔中沒有涉及完整性和保密性,這些問題將在以后的版本中描述。

9. 參考文獻

[1] S. Bradner, "The Internet Standards Process -- Revision 3", RFC2026, Harvard University, October 1996
[2] S. Bradner, "Key words for use in RFCs to Indicate Requirement Levels", RFC 2119, Harvard University, March 1997
[3] E. Whitehead, M. Murata, "XML Media Types", RFC2376, UC Irvine, Fuji Xerox Info. Systems, July 1998
[4] T. Berners-Lee, R. Fielding, L. Masinter, "Uniform Resource Identifiers (URI): Generic Syntax", RFC 2396, MIT/LCS, U.C.Irvine, Xerox Corporation, A ugust 1998.
[5] R. Fielding, J. Gettys, J. C. Mogul, H. Frystyk, T. Berners-Lee, "Hypert ext Transfer Protocol -- HTTP/1.1", RFC 2616, U.C. Irvine, DEC W3C/MIT, DEC,W3C/MIT, W3C/MIT, January 1997
[6] H. Nielsen, P. Leach, S. Lawrence, "An HTTP Extension Framework", RFC 2774, Microsoft, Microsoft, Agranat Systems
[7] W3C Recommendation "The XML Specification"
[8] W3C Recommendation "Namespaces in XML"
[9] W3C Working Draft "XML Linking Language". This is work in progress.
[10] W3C Working Draft "XML Schema Part 1: Structures". This is work in progress.
[11] W3C Working Draft "XML Schema Part 2: Datatypes". This is work in progress.
[12] Transfer Syntax NDR, in "DCE 1.1: Remote Procedure Call"
[13] N. Freed, N. Borenstein, "Multipurpose Internet Mail Extensions (MIME)Part One: Format of Internet Message Bodies", RFC2045, Innosoft, First Virtu al, November 1996

10。 附錄

A. SOAP封裝舉例

A.1 請求編碼舉例

例5 類似于例1,但有一個必要的頭

POST /StockQuote HTTP/1.1
Host: www.stockquoteserver.com
Content-Type: text/xml; charset="utf-8"
Content-Length: nnnn
SOAPAction: "Some-URI"
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
<SOAP-ENV:Header>
<t:Transaction
xmlns:t="some-URI"
SOAP-ENV:mustUnderstand="1">
5
</t:Transaction>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<m:GetLastTradePrice xmlns:m="Some-URI">
<symbol>DEF</symbol>
</m:GetLastTradePrice>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

例6 類似于例1,但有多個請求參數

POST /StockQuote HTTP/1.1
Host: www.stockquoteserver.com
Content-Type: text/xml; charset="utf-8"
Content-Length: nnnn
SOAPAction: "Some-URI"
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
<SOAP-ENV:Body>
<m:GetLastTradePriceDetailed
xmlns:m="Some-URI">
<Symbol>DEF</Symbol>
<Company>DEF Corp</Company>
<Price>34.1</Price>
</m:GetLastTradePriceDetailed>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

A.2 應答編碼舉例

例7 與例2類似,但有必要的頭部

HTTP/1.1 200 OK
Content-Type: text/xml; charset="utf-8"
Content-Length: nnnn
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
<SOAP-ENV:Header>
<t:Transaction xmlns:t="some-URI" xsi:type="xsd:int" mustUnderstand="1"> 5 </t:Transaction>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<m:GetLastTradePriceResponse xmlns:m="Some-URI">
<Price>34.5</Price>
</m:GetLastTradePriceResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

例8 與例2類似,但有一個結構

HTTP/1.1 200 OK
Content-Type: text/xml; charset="utf-8"
Content-Length: nnnn
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
<SOAP-ENV:Body>
<m:GetLastTradePriceResponse
xmlns:m="Some-URI">
<PriceAndVolume>
<LastTradePrice> 34.5 </LastTradePrice>
<DayVolume> 10000 </DayVolume>
</PriceAndVolume>
</m:GetLastTradePriceResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

例9 與例2類似,但處理必要的頭出錯

HTTP/1.1 500 Internal Server Error
Content-Type: text/xml; charset="utf-8"
Content-Length: nnnn
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<faultcode>SOAP-ENV:MustUnderstand</faultcode>
<faultstring>SOAP Must Understand Error</faultstring>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

例10 與例2類似,但處理Body出錯

HTTP/1.1 500 Internal Server Error
Content-Type: text/xml; charset="utf-8"
Content-Length: nnnn
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<faultcode>SOAP-ENV:Server</faultcode>
<faultstring>Server Error</faultstring>
<detail>
<e:myfaultdetails xmlns:e="Some-URI">
<message>
My application didn't work
</message>
<errorcode> 1001 </errorcode>
</e:myfaultdetails>
</detail>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>



禮物 2007-11-09 22:11 發表評論
]]>
SOAP凈化有線協議(四):簡化客戶程序http://www.aygfsteel.com/libin2722/articles/159459.html禮物禮物Fri, 09 Nov 2007 14:07:00 GMThttp://www.aygfsteel.com/libin2722/articles/159459.htmlhttp://www.aygfsteel.com/libin2722/comments/159459.htmlhttp://www.aygfsteel.com/libin2722/articles/159459.html#Feedback0http://www.aygfsteel.com/libin2722/comments/commentRss/159459.htmlhttp://www.aygfsteel.com/libin2722/services/trackbacks/159459.html
接下來我將介紹一個以動態代理類為基礎的框架,這個框架使得SOAP(簡單對象訪問協議)客戶程序的創建更加簡單和直觀。SOAP是一種用XML編碼數據的有線協議。在本系列文章的第二篇、第三篇構造SOAP服務的過程中,我們發現客戶程序的開發者必須多做許多原來不必做的工作。為幫助回憶,你可以看一下第二篇文章中的SOAP服務代碼,看看和客戶程序代碼相比較時,服務程序的SOAP代碼是多么微不足道。本系列文章前幾篇所創建的簡單SOAP服務顯示出,基于SOAP的服務只包含無論用不用SOAP都必須提供的代碼。服務程序的開發者要編寫的額外代碼很少,而客戶程序開發者卻有許多額外工作要做。本文介紹的類將把這些額外工作減到最少。

一、介紹SOAP代理類
首先,我要給出如果客戶程序使用了本文創建的框架,它將變成什么樣子:

package hello;
import soapproxy.*;
public class Client
{
public static void main(String[] args)
{
try
{
Class[] interfaces = new Class[] {hello.Hello.class};
Hello hello = (Hello)(Proxy.newInstance("urn:Hello",interfaces));

// 調用sayHelloTo方法
// 這個sayHelloTo方法需要一個字符串參數
System.out.println(hello.sayHelloTo("John"));

// 調用sayHelloTo方法
// 這個sayHelloTo方法需要一個Name JavaBean參數
Name theName = new Name();
theName.setName("Mala");
System.out.println(hello.sayHelloTo(theName));
}
catch(Exception e)
{
e.printStackTrace();
}
}
}


也許是出于我的個人愛好,我認為上面的客戶代碼比第二篇和第三篇文章中的客戶代碼更好。如果你現在不能理解上面的代碼,這很正常,但我想待到本文結束時你會理解的。

要理解客戶程序的代碼,你必須深入了解SOAP Proxy類,它在soapproxy包內,可以在Proxy.java內找到(參見本文最后的參考資源)。Proxy類有一個私有的構造函數,它意味著Proxy實例不能從Proxy之外創建;新建Proxy實例的唯一方法是通過靜態的newInstance()方法。newInstance()方法有兩個參數:SOAP服務的對象ID,以及一個數組,數組中包含一組該代理要實現的接口的名字。對象ID很簡單,但這些接口名字是什么?從哪里去得到這些接口的名字?SOAP服務的開發者直接把服務上所有可被客戶程序調用的方法堆在一起得到一個接口。相當簡單,不是嗎?

現在我們為HelloWorld服務定義一個接口。第二篇文章中,這個服務的最終版本有sayHelloTo()方法的兩個重載版本:一個版本的參數是一個字符串,另一個版本的參數是一個Name JavaBean。這兩個方法就可以構成一個接口,稱為Hello,如下所示:

package hello;
public interface Hello
{
public String sayHelloTo(String name);
public String sayHelloTo(Name name);
}

服務開發者決定要創建多少接口,以及為這些接口取什么樣的名字。例如,你可以為HelloWorld服務創建兩個接口,每一個接口包含一個方法。一般地,你應該避免創建方法數量大于七個的接口。另外,注意只把那些看來有必要放在一起的方法用一個接口組織起來。例如,如果HelloWorld服務還有一個返回定制的Good-Bye消息給調用者的sayByeTo()方法,設計兩個獨立的接口也許比較明智:一個接口用于sayHelloTo()方法,一個接口用于sayByeTo()方法。

現在我們有了定義HelloWorld服務和客戶程序之間契約的接口,下面返回來看newInstance()方法。如前所述,newInstance()方法創建Proxy類的一個新實例。newInstance()方法可以創建新實例是因為它屬于Proxy類,能夠訪問私有的構造函數。newInstance()方法為新創建的實例調用initialize()方法。initialize()值得關注,因為動態代理就是在這里創建和返回。initialize()的代碼如下所示:

private Object initialize(Class[] interfaces)
{
return(java.lang.reflect.Proxy.newProxyInstance(getClass().getClassLoader()
,interfaces,this));
}

注意newProxyInstance()方法的應用。創建動態代理類實例的唯一辦法是調用該類(即java.lang.reflect.Proxy類)靜態的newProxyInstance()方法。java.lang.reflect.Proxy類為創建動態代理類提供了靜態方法,而且它還是所有由這些方法創建的動態代理類的超類。換句話說,它不僅是一個創建動態代理類的工廠,而且它本身也是一個動態代理類!因此,在我們的例子中,SOAP代理不是動態代理;相反,這個動態代理實際上是newProxyInstance靜態方法返回的java.lang.reflect.Proxy類的一個實例。從本文后面可以看到,這個動態代理實際上通過SOAP代理實現的invoke()方法完成它的所有工作。那么,這個動態代理如何建立和SOAP代理的聯系呢?因為有一個對SOAP代理的引用傳遞給了newProxyInstance()方法。也許現在這聽起來有點費解,但只要你分析一下invoke()方法,這一切就很明白了。

java.lang.reflect.Proxy類構造函數的第一個參數是一個類裝載器實例,第二個參數是需要動態實現的接口的數組(它就是客戶程序傳遞給newInstance()的數組),第三個參數是一個實現了java.lang.reflect.InvocationHandler接口的類的實例。因為SOAP Proxy類實現了InvocationHandler接口,所以第三個參數是代理實例本身(即this)。InvocationHandler接口有一個方法invoke()。當動態代理的動態實現的接口被調用時,Java運行時環境調用invoke()方法。因此,舉例來說,當客戶程序調用動態代理的Hello接口的sayHelloTo()方法時,Java運行時環境將調用SOAP代理的invoke()方法。

你可能已經發現,SOAP代理的newInstance()方法不返回SOAP代理的實例;相反,它返回newInsance()剛剛創建的動態代理,而動態代理動態地實現客戶程序傳入的接口數組。客戶程序可以將這個返回的動態代理定型為傳入newInstance()的任意接口類型,在動態代理上調用接口所定義的各個方法,就象動態代理真地實現了那些接口一樣。

.
.
try
{
Class[] interfaces = new Class[] {hello.Hello.class};
Hello hello = (Hello)(Proxy.newInstance("urn:Hello",interfaces));
// 調用參數為字符串的sayHelloTo方法
System.out.println(hello.sayHelloTo("John"));
// 調用參數為Name JavaBean的sayHelloTo方法
Name theName = new Name();
theName.setName("Mala");
System.out.println(hello.sayHelloTo(theName));
}
.
.

在上面的代碼中,invoke()方法將被調用兩次,每次調用sayHelloTo()方法時執行一次。現在我們來看看invoke()方法。簡而言之,invoke()方法的工作正是第二篇文章中每一個客戶程序必須手工完成的工作,其中包括:用合適的調用參數設置一個Call對象,定制的調用參數所需要的類型映射。由于SOAP代理中的invoke()方法擔負了所有這些任務,客戶程序釋放了這份負擔。

在invoke()方法接收到的三個參數中,我們只對后面兩個感興趣。第二個參數,即Method對象,給出了被調用方法的名字。記住,被調用方法的名字對應著一個SOAP服務導出的已知方法。服務的對象ID作為參數傳遞給newInstance()方法,所以invoke()方法已經擁有該對象ID。invoke()方法利用這些信息,按照如下方式設置Call對象:

Call call = new Call();
call.setTargetObjectURI(urn);
call.setMethodName(m.getName());
call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC);

現在要做的是為遠程服務調用設置參數。為此,我們要用到invoke()方法的第三個參數:傳入動態代理上被調用方法的一個參數數組。數組中索引為0的參數是方法調用中最左邊的參數,索引為1的參數是方法的第二個參數,依此類推。舉例來說,如果客戶程序調用了sayHelloTo(String name)方法,那么參數數組就是包含一個字符串的數組。invoke()方法處理該數組的每一個元素,創建一個由Parameter對象構成的向量(Vector)(正如第二篇文章中客戶程序所做的那樣):

java.util.Vector params = new java.util.Vector();
for( int i=0; i&lt;args.length; i++ )
{
if( isSimple(args[i]) || isSimpleArray(args[i]) )
{
params.add(new Parameter(_paramName+(i+1),
args[i].getClass(),args[i],null));
}
else if( isVector(args[i]) )
{
addMapping((java.util.Vector)args[i]);
params.add(new
Parameter(_paramName+(i+1),args[i].getClass(),args[i],null));
}
// 如果這個數組的元素不屬于Java基本數據類型
// 則假定這是一個JavaBean的數組
else if( isArray(args[i]) )
{
if( smr == null )
smr = new SOAPMappingRegistry();
if( beanSer == null )
beanSer = new BeanSerializer();

ArraySerializer arraySer = new ArraySerializer();
smr.mapTypes(Constants.NS_URI_SOAP_ENC,
null, null, beanSer, beanSer);
smr.mapTypes(Constants.NS_URI_SOAP_ENC,
null,args[i].getClass(), arraySer, arraySer);
params.add(new Parameter(_paramName+(i+1),
args[i].getClass(),args[i],null));
}
// 假定這是一個Bean
else
{
if( smr == null )
smr = new SOAPMappingRegistry();
if( beanSer == null )
beanSer = new BeanSerializer();
String qnamePart = args[i].getClass().getName();
smr.mapTypes(Constants.NS_URI_SOAP_ENC,
new QName(urn, qnamePart),args[i].getClass(), beanSer,
beanSer);
params.add(new Parameter(_paramName+(i+1),args[i].getClass(),args[i],null));
}
}


invoke()方法用到了許多私有的輔助方法,比如用isSimple()來確定參數的類型。如果參數是一個JavaBean或者一個數組,那么,程序必須設置一個定制的SOAP映射注冊項,并通過setSOAPMappingRegistry()方法對Call對象作相應的設置(參見第二篇文章)。SOAP代理假定,當出現JavaBean時,SOAP服務用到的所有JavaBean按照如下方式映射:NameSpace URI設置成對象ID,Local Part設置成JavaBean完整的類名。我們部署HelloWorld服務時正是按照這個要求進行,所以一切都不存在問題。

invoke()方法的剩余部分相當簡單:設置Call對象參數,設置定制SOAP映射注冊項(如果有必要的話),發出調用,接收方法調用的返回值。如下所示:

if( params.size() != 0 )
call.setParams(params);
if( smr != null )
call.setSOAPMappingRegistry(smr);
// 發出調用
Response resp = call.invoke(serverURL, "");
if( !resp.generatedFault() )
{
Parameter ret = resp.getReturnValue();
return(ret.getValue());
}
else
{
Fault fault = resp.getFault();
throw new
SOAPException(fault.getFaultCode(),fault.getFaultString());
}


二、HelloWorld服務
下面是HelloWorld服務的完整代碼。有似曾相識的感覺嗎?

package hello;
public class HelloServer
{
public String sayHelloTo(String name)
{
System.out.println("sayHelloTo(String name)");
return "Hello " + name + ", How are you doing?";
}
public String sayHelloTo(Name theName)
{
System.out.println("sayHelloTo(Name theName)");
return "Hello " + theName.getName() + ", How are you doing?";
}
}


回憶一下,Name是一個簡單的JavaBean,代碼如下:

package hello;

public class Name
{
private String name;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
}


事實上,這里服務的代碼與第二篇文章中的服務程序代碼完全一樣。對于服務開發者來說,唯一增加的工作是創建Java接口。部署服務的方法也和第二篇文章中討論的完全一樣,所以這里我不再介紹。相同的地方還不止如此,編譯和運行客戶程序的方法也和第二篇文章介紹的一樣。為什么有這么多相同之處呢?因為我們創建的代理是一個非插入式的框架,它不會修改和干涉任何Apache SOAP部件的內部工作——無論是客戶端還是服務端。

三、其他說明
本文討論的SOAP代理(可以從文章后面下載)支持以下參數類型:

⑴ 下面的Java基本數據類型及其對應的對象形式。

boolean, Boolean,
double, Double,
float, Float,
long, Long,
int, Integer,
short, Short,
byte, Byte


注:服務器端總是接收基本數據類型。

⑵ 任何JavaBean

注:

  • 該JavaBean不能包含其他JavaBean。
  • 如果數組或向量包含除字符串或1列出數據類型之外的類型,則JavaBean不能包含這類數組或向量。
      ⑶ 下面的類:String, Vector

      注:

    • Vector可以包含1、2列出的所有類型和字符串。
    • 服務器端把Vector作為一個對象的數組接收。
        ⑷ 數組。數組元素可以是在1、2中列出的所有類型和字符串(上面已注明的除外)。

        ■ 結束語
        在這個四篇文章構成的系列中,我不僅介紹了SOAP的基礎知識,而且介紹了SOAP 1.1標準的一個優秀的實現:Apache SOAP。在本文中,我提供了一個以動態代理類為基礎的框架,這個框架極大地簡化了使用Apache SOAP的客戶程序開發者的工作。

        我深切地感到SOAP有著美好的前景,至少有兩個理由使我這么認為:首先,SOAP以一些開放的標準為基礎,比如XML。這使得無論是Microsoft,還是反Microsoft的企業,都廣泛地接受了SOAP。對于開發者來說,這無疑是一個天大的好消息。第二,SOAP正在成為其他許多標準的基礎,比如UDDI(Universal Description,Discovery,and Integration)。許多人認為,Web服務代表著下一代的Web應用開發,而SOAP和UDDI都是Web服務的關鍵組成部分。

        ■ 參考資源
      • 下載本文的完整代碼:JavaAndSOAP4_code.zip
      • W3C的SOAP 1.1規范:
      • http://www.w3.org/TR/SOAP/
      • 有關動態代理類的更多信息:
      • http://java.sun.com/j2se/1.3/docs/guide/reflection/proxy.html
      • 關于IBM SOAP工程的更多信息:
      • http://www.alphaworks.ibm.com/tech/soap4j
      • 下載Apache SOAP:
      • http://xml.apache.org/dist/soap/


      • 禮物 2007-11-09 22:07 發表評論
        ]]>
        SOAP凈化有線協議(三):用腳本語言編寫服務http://www.aygfsteel.com/libin2722/articles/159458.html禮物禮物Fri, 09 Nov 2007 14:05:00 GMThttp://www.aygfsteel.com/libin2722/articles/159458.htmlhttp://www.aygfsteel.com/libin2722/comments/159458.htmlhttp://www.aygfsteel.com/libin2722/articles/159458.html#Feedback0http://www.aygfsteel.com/libin2722/comments/commentRss/159458.htmlhttp://www.aygfsteel.com/libin2722/services/trackbacks/159458.html
        SOAP(簡單對象訪問協議)是一種利用XML編碼數據的有線協議,它為Java的平臺無關性、可移植性帶來了更高層次的協同操作能力。在這個關于SOAP的系列文章的第二篇中,我介紹了Apache SOAP。作為SOAP規范的實現之一,Apache SOAP簡化了SOAP應用的構造。我們曾經用Apache SOAP構造了兩個簡單的HelloWorld服務,以及調用這些服務的sayHelloTo()方法的客戶程序。我們看到,雖然創建SOAP服務相當簡單,但客戶程序必須完成許多額外的工作,例如它必須設置Call對象,調用Call對象的invoke()方法,然后分析作為調用結果返回的Response對象。

        其實,用Apache SOAP創建SOAP服務的過程還可以變得更簡單,唯一的前提是:你必須懂得規定的幾種腳本語言之一。對我來說——以及對大多數Java開發者來說,幸運的是,這些腳本語言中包含了JavaScript。確實不錯,在Apache SOAP中,你可以用JavaScript創建SOAP服務。本文要介紹的就是用JavaScript創建SOAP服務的具體做法。

        一、重新構造Apache SOAP
        Apache SOAP的腳本支持建立在Bean Scripting Framework(BSF,Bean腳本框架)的基礎之上。BSF原先由IBM開發,現在作為一個源代碼開放的工程發行,它使得Java程序能夠運行用其他語言編寫的腳本,也使得其他腳本語言能夠使用已有的Java類。Apache SOAP利用了BSF的前一種能力。從Apache網站下載的標準二進制版本不支持腳本。具體地說,soap.jar不包含org.apache.soap.server.InvokeBSF類,而這個類是Apache SOAP和BSF的結合點和接口。Apache SOAP的開發者知道,并非每一個使用SOAP的人都需要BSF,也并非每一個人都安裝了腳本引擎,所以在soap.jar中省略了腳本支持。要想用腳本編寫SOAP服務,你必須從源代碼重新構造以便引入InvokeBSF類。

        首先要從http://xml.apache.org/dist/soap/下載源代碼(soap-src-2.0.zip)。然后,把下載得到的文件解壓縮到Apache SOAP所安裝的目錄。在我這里,它是E:驅動器的根目錄。完成后,你將在soap_2-0目錄下得到一個src子目錄,子目錄中包含Apache SOAP的所有源代碼。重新從源代碼構造Apache SOAP之前,你還必須下載必需的BSF jar文件。在ftp://ftp.mozilla.org/pub/js/可以找到一個。請結合Mozilla的JavaScript引擎Rhino使用它,Rhino可以從http://www.mozilla.org/rhino/download.html下載一個ZIP文件得到。我把這個文件解壓縮到E:盤根目錄下,最終得到一個包含了Rhino的E:\rhino目錄,我們感興趣的是它的js.jar。

        接下來,你需要一個實際執行重新構造操作的工具,即Ant。Ant也是一個Apache的軟件工程,它是一個基于Java的工具。Ant實際上和創建Web服務器Tomcat的工程同屬一個工程,即Jakarta。在Ant中,所有構造信息,例如構造目標、依賴關系等,都通過XML配置文件指定,這是Ant獨一無二的特點。此外,Ant是可擴展的。請參見本文最后“參考資源”部分收錄的文章,了解如何充分發揮Ant的潛能。你可以從參考資源提供的鏈接下載Ant,然后解開壓縮(我把它放入了C:盤的根目錄)。

        現在,從Apache SOAP安裝目錄執行如下命令:

        set
        CLASSPATH=E:\jakarta-tomcat\lib\servlet.jar;E:\xerces-1_2_0\xerces.jar;
        E:\soap-2_0\lib\bsf.jar
        C:\build\bin\ant


        由于上面的命令沒有指定一個XML配置文件,Ant批命令文件將在當前目錄(在我這里,是E:\soap_2-0)中尋找一個名為build.xml的文件。Apache SOAP提供了這個文件。打開這個文件可以看到,只有當com.ibm.bsf.BSFManager在classpath中時,InvokeBSF類才會編譯。這就是我把bsf.jar(它包含了BSFManager類)放入類路徑的原因。把新構造出來的soap.jar文件從build\lib子目錄復制到lib子目錄(我建議修改原來的soap.jar文件進行備份)。最后,把bsf.jar和js.jar加入到Web服務器的類路徑。

        大功告成!現在你可以開始用腳本編寫SOAP服務了。

        二、用JavaScript編寫HelloWorld應用
        現在,我們用JavaScript重新編寫第二篇文章的HelloWorld服務。服務程序的完整代碼如下:

        function sayHelloTo(name)
        {
        return "Hello " + name + ", How are you?";
        }


        還有比這更容易的事情嗎?不過,不要讓這簡單易行欺騙了你。事實上,你可以在服務程序里進行相當復雜的處理。例如,你可以從腳本代碼訪問任何標準的Java類。請看下面經過修改的腳本代碼,它輸出服務器的時間:

        function sayHelloTo(name)
        {
        var today = new java.util.Date();
        java.lang.System.out.println("Today is " + today.toString());
        return "Hello " + name + ", How are you?";
        }


        另外,你還可以導入和使用自己的任意Java類。例如,我們可以修改腳本代碼,讓它使用Name JavaBean:

        importClass(Packages.hello.Name);
        function sayHelloTo(name)
        {
        var today = new java.util.Date();
        java.lang.System.out.println("Today is " + today.toString());
        var beanName = new Name();
        beanName.setName("John");

        java.lang.System.out.println(beanName.getName());
        return "Hello " + name + ", How are you?";
        }


        三、部署服務
        在使用JavaScript版本的服務之前,首先要部署它。正如本系列文章的第二篇所介紹的,在Apache SOAP中部署服務有兩種辦法:使用Web界面的管理工具,或者從命令行部署服務。下面我們來看看兩種辦法的具體操作過程。

        3.1 使用Web界面的管理工具
        要使用Web界面的管理工具,用瀏覽器打開http://localhost:8080/apache-soap/admin。點擊窗口左邊的Deploy按鈕。記住,ID輸入框用來設置對象ID,SOAP基礎設施利用對象ID把RPC(遠程過程調用)請求關聯到SOAP服務。每一個Apache SOAP服務都必須有一個對象ID,而且這個對象ID必須在該服務器上部署的所有服務之間唯一。把ID設置成urn:Hello,這個ID也就是我們在第二篇文章中為服務設置的對象ID。

        把Scope輸入框設置成application。回顧一下,Scope輸入框用來指定響應調用請求的服務實例的生存時間(請參考第二篇文章中的更多說明)。

        在Methods輸入框中輸入當前部署的服務允許調用的方法名字,多個方法名字之間以空白字符分隔。我們的服務只支持一個方法,即sayHelloTo()。

        由于服務用JavaScript實現,而不是象第二篇文章那樣用Java實現,所以Provider Type輸入框應該填script。相應地,Java Provider輸入框(包括Provider Class和Static輸入框)不必填寫。但現在必須填寫Script Provider輸入框,選擇JavaScript(Rhino)作為腳本語言。由于我們將在Script文本輸入框中提供腳本正文,所以現在不用填寫Script Filename輸入框。把下面的腳本代碼復制到Script輸入框:

        importClass(Packages.hello.Name);
        function sayHelloTo(name)
        {
        var today = new java.util.Date();
        java.lang.System.out.println("Today is " + today.toString());
        var beanName = new Name();
        beanName.setName("John");

        java.lang.System.out.println(beanName.getName());
        return "Hello " + name + ", How are you?";
        }


        現在滾動到屏幕的最下面,點擊表單下面的Deploy按鈕(不是窗口左邊的Deploy按鈕)。要驗證服務已經成功部署,點擊窗口左邊的List按鈕。這時,urn:Hello服務應該在服務清單中出現。點擊這個服務,確認所有信息都與剛才輸入的吻合。

        3.2 從命令行部署服務
        要從命令行部署服務,所有部署信息必須放入一個XML部署描述器文件。下面是我用來部署該服務的XML部署描述器文件:

        <isd:service xmlns:isd="http://xml.apache.org/xml-soap/deployment"
        id="urn:Hello">
        <isd:provider type="script" scope="Application" methods="sayHelloTo">
        <isd:script language="javascript">
        importClass(Packages.hello.Name);
        function sayHelloTo(name)
        {
        var today = new java.util.Date();
        java.lang.System.out.println("Today is " + today.toString());
        var beanName = new Name();
        beanName.setName("John");

        java.lang.System.out.println(beanName.getName());
        return "Hello " + name + ", How are you?";
        }
        </isd:script>
        </isd:provider>
        </isd:service>


        和第二篇文章用到的部署描述器文件相比,這里主要的不同在于把提供者類型設置成了script而不是java。由于這個原因,部署描述器文件不再指定一個Java類,而是提供了服務的腳本代碼。

        部署服務之前應當確保Web服務器已經啟動。下面的代碼顯示了如何部署服務:

        java org.apache.soap.server.ServiceManagerClient
        http://localhost:8080/apache-soap/servlet/rpcrouter deploy
        DeploymentDescriptor.xml


        DeploymentDescriptor.xml是前面介紹的包含部署描述信息的XML文件。要驗證服務已經成功部署,執行下面的命令:

        java org.apache.soap.server.ServiceManagerClient
        http://localhost:8080/apache-soap/servlet/rpcrouter query urn:Hello


        這時,我們應該看到和DeploymentDescriptor.xml文件一樣的內容。

        四、測試
        我們用第二篇文章提供的客戶程序Client.java來測試HelloWorld服務。為什么可以用同一個客戶程序訪問JavaScript編寫的服務呢?因為客戶程序完全不會在乎服務用什么語言編寫。只要服務能夠理解SOAP請求,能夠返回SOAP應答,客戶程序不會關注服務用什么方式實現。回顧一下,下面就是我用來運行hello.Client的批命令文件:

        set
        CLASSPATH=E:\soap-2_0\samples\;E:\soap-2_0\lib\soap.jar;
        E:\xerces-1_2_0\xerces.jar
        java hello.Client Tarak


        觀察Web服務器的控制臺窗口,每次運行客戶程序的時候,我們都可以看到當前的日期和輸出“John”。

        ■ 結束語
        在這篇文章中,我介紹了Apache SOAP實現所提供的腳本語言支持。為什么說它很重要呢?只要分析一下為何Web開發如此流行。在我看來,一個關鍵的原因在于Web開發已經成熟,幾乎任何人都能夠用HTML和JavaScript之類的簡單腳本語言構造出復雜的Web頁面。類似地,在Web開發的服務器端,人們可以使用JSP這類易學但強大的腳本語言。我認為這種推理同樣適用于SOAP開發。如果SOAP想要挺進主流,獲得絕大部分人的支持,那么它應該盡量地簡化。Apache SOAP加入對腳本的支持正是為了這個目標;它顯著地擴展了創建SOAP服務的開發者的范圍。

        不過,不要忘了還有另一個因素需要考慮:客戶端開發者,即調用SOAP服務的開發者。如前所述,Apache SOAP的客戶端開發者比較“吃虧”,反而增加了一些原本不必做的工作。因此,在本系列文章的下一篇也即最后一篇中,我將介紹一個框架,它以Java 2平臺1.3版本新引入的動態代理類為基礎,使得創建客戶程序就象創建SOAP服務一樣簡單直觀。

        禮物 2007-11-09 22:05 發表評論
        ]]>
        SOAP凈化有線協議(二):Apache SOAP介紹http://www.aygfsteel.com/libin2722/articles/159455.html禮物禮物Fri, 09 Nov 2007 14:03:00 GMThttp://www.aygfsteel.com/libin2722/articles/159455.htmlhttp://www.aygfsteel.com/libin2722/comments/159455.htmlhttp://www.aygfsteel.com/libin2722/articles/159455.html#Feedback0http://www.aygfsteel.com/libin2722/comments/commentRss/159455.htmlhttp://www.aygfsteel.com/libin2722/services/trackbacks/159455.html
        SOAP對于Java開發者來說尤其重要,因為它讓平臺無關和可移植的Java程序更容易協同操作,使得Java的寶貴特性進一步增值。事實上,如果Java 2平臺企業版(J2EE)的下一個版本讓SOAP成為一種必須遵循的有線協議,規定所有遵從J2EE規范的應用服務器都必須支持SOAP協議,我也不會感到奇怪。不過就現在來說,我想我的猜想應該暫停了。

        這個系列的文章總共四篇,這是第二篇。在這里,我要介紹的是Apache SOAP實現。

        一、Apache SOAP概述
        Apache SOAP,即Apache Software Foundation對SOAP規范的實現,建立在IBM的SOAP4J的基礎上。和所有其他Apache工程類似,Apache SOAP源代碼開放,按照Apache許可協議發行。我覺得這是當前最好的SOAP實現之一。然而,雖然Apache SOAP遵從SOAP規范1.1版本,但它缺乏SOAP 1.1所包含的某些功能(參見本文最后的“參考資源”,了解Apache SOAP支持功能的清單)。

        1.1、下載和安裝Apache SOAP
        如前所述,Apache SOAP可以免費下載(參見“參考資源”中提供的下載鏈接)。我為我的Windows NT便攜機下載了soap-bin-2.0.zip文件,該文件包含Apache SOAP 2.0,這是寫作本文時的最新版本。安裝Apache SOAP可謂輕而易舉,共包含如下三個簡單的步驟:

        1. 解開下載所得文件的ZIP壓縮:解開壓縮之后就得到了一個soap-2_0子目錄。我把ZIP文件的內容解壓縮到E盤的根目錄下,因此有了一個包含Apache SOAP的E:\soap-2_0目錄。
        2. 配置Web環境:要有一個支持Servlet和JSP的Web服務器。至此,你可能遇到下面兩種情況之一:
          • 情況1:已經有一個支持Servlet和JSP的Web服務器,而且你覺得配置這個服務器沒有問題。在這種情況下,請配置服務器,使得瀏覽器可以訪問http://localhost:8080/apache-soap/,打開soap-2_0 \webapps\soap\目錄下面的index.html文件。
          • 情況2:沒有支持Servlet和JSP的Web服務器,或者雖然有這樣一個服務器,卻不想拿它做試驗。在這種情況下,我建議你下載Tomcat的最新版本(寫作本文時,最新版本是3.1)(參見“參考資源”中的鏈接)。Tomcat是Apache創建和免費提供給軟件開發者的又一個優秀軟件。下載合適的ZIP文件之后(jakarta-tomcat-3.1.1.zip),解開壓縮時創建一個jakarta-tomcat子目錄。和前面相似,我把解壓縮得到的文件放入E盤的根目錄之下。在jakarta-tomcat\conf\server.xml配置文件中增加一個新的<Context>標記,如下所示:
            <Context path="/apache-soap" docBase="E:/soap-2_0/webapps/soap"
            debug="1" reloadable="true">
            </Context>
            在Context元素的docBase屬性中,你應該在指定soap-2_0目錄時把E:替換成合適的盤符。要啟動Tomcat,執行startup.bat(對于Unix,執行startup.sh)。要關閉Tomcat,執行shutdown.bat(對于Unix,執行shutdown.sh)。但請稍等——現在請不要啟動Tomcat。
        3. 設置Web服務器classpath:Apache SOAP要求有1.1.2版本或更高的Apache Xerces(Java),它支持DOM(文檔對象模型)Level 2規范,支持名稱空間。我使用1.2版本的Xerces,即Apache網站的Xerces-J-bin.1.2.0.zip。解開這個壓縮文件,得到xerces-1_2_0子目錄。和前面一樣,我把解壓縮得到的文件放到了E:盤的根目錄之下。你應該配置Web服務器,使它能夠用xerces.jar(它在xerces-1_2_0子目錄下)進行所有XML解析——而不是用服務器附帶的庫或jar解析XML。例如,Tomcat附帶了一個XML解析器(jakarta-tomcat\lib\xml.jar),支持DOM Level 1接口。即使你把xerces.jar放入了classpath,Tomcat下運行的Java代碼也可能找錯接口,因為在用來啟動Tomcat的Shell腳本/批命令文件中,xerces.jar被放到了classpath的最后。因此,必須編輯jakarta-tomcat\bin目錄下的tomcat.bat(對于Unix,則是tomcat.sh),把xerces.jar放到classpath的前面。下面是我在jakarta-tomcat\bin\tomcat.bat文件中所作的修改:
          set CLASSPATH=E:\xerces-1_2_0\xerces.jar;%CLASSPATH%;%cp%
        如果你在第二個步驟中屬于情況2,也必須配置服務器,使它能夠使用xerces.jar。

        不管你屬于哪一種情況,除了配置xerces.jar之外,你還必須配置Web服務器的classpath使它能夠使用soap-2_0\lib\目錄下的soap.jar。

        1.2、檢查安裝是否成功
        現在,啟動Web服務器,用瀏覽器打開http://localhost:8080/apache-soap/admin,驗證安裝是否成功。這時,你應該看到下圖所示的管理屏幕。


        圖一:Web界面的Apache SOAP管理工具

        二、實例:HelloWorld
        現在你已經設置好了Apache SOAP,我們來實際使用一下,構造一個簡單的HelloWorld應用。在SOAP術語中,應用稱為服務。一般地,創建服務分兩個步驟,這兩個步驟可能由同一個人或組織實施,也可能不是。第一個步驟是在選定的語言中定義和實現服務本身,這里我們選擇Java語言。第二個步驟是創建實際調用服務的客戶程序。首先我們來看HelloWorld服務。

        2.1、HelloWorld服務
        我在第一篇文章中討論了一個用SOAP實現的HelloWorld服務實例。這個服務要求輸入一個用戶名字,返回一個定制的Hello消息給調用者。下面的代碼顯示了HelloWorld服務的完整Java代碼。

        package hello;
        public class HelloServer
        {
        public String sayHelloTo(String name)
        {
        System.out.println("sayHelloTo(String name)");
        return "Hello " + name + ", How are you doing?";
        }
        }


        這就是全部的代碼嗎?如果這是真的話,實在太簡單了!

        Apache SOAP使創建服務變得極其簡單。服務主要由業務邏輯構成,不管服務以哪種方式提供給外界使用,業務邏輯代碼都是必須編寫的。換句話說,服務不會和任何SOAP相關的代碼混合,因為Apache SOAP底層體系——它由rpcrouter Servlet和soap.jar構成——幫助我們完成了所有錯綜復雜的工作。我們來簡要地探討一下這些錯綜復雜的工作究竟包含些什么,例如,Apache SOAP如何處理HTTP上的遠程過程調用(RPC)請求?理解這一點將給創建客戶程序帶來方便(不錯,是客戶程序)。

        在Apache SOAP中,org.apache.soap.rpc包支持在SOAP上進行RPC調用。Apache RPC支持的關鍵在于對象ID。所有的Apache SOAP服務必須有一個唯一的ID,它被視為服務的對象ID。眾所周知,唯一性是一個相對的概念;在Apache SOAP中,對象ID的唯一性相對于服務所部署的Apache SOAP服務器而言。也就是說,部署在不同Apache SOAP服務器上的兩個服務可能有同樣的對象ID。

        想要使用服務的客戶程序設置一個org.apache.soap.rpc.Call對象,指定目標服務的對象ID、待調用方法的名字以及提供給方法的參數(如果有的話)。設置好Call對象之后,客戶程序調用它的invoke()方法。invoke()方法需要兩個參數,第一個參數是一個執行rpcrouter Servlet的URL,如http://localhost:8080/apache-soap/servlet/rpcrouter;第二個參數是SOAPAction頭(請參考本系列的第一篇文章,了解SOAPAction頭的重要性和可能的取值)。

        invoke()方法把Call對象轉換成XML SOAP請求(類似第一篇文章所提供的示例),把請求發送給第一個參數中的URL所指定的rpcrouter Servlet。當Servlet返回應答,invoke()方法返回一個org.apache.soap.rpc.Response對象,這個對象包含了服務的應答(如果有的話)或錯誤信息(如果出現了錯誤)。HTTP規定每一個請求都必須有一個應答;因此,即使服務本身不返回任何東西,rpcrouter Servlet總是會返回一些內容。因此,invoke()方法總是返回一個Response對象。

        在服務端,Apache SOAP服務器(也就是rpcrouter Servlet)接收客戶程序發送的SOAP請求,重新構造出Call對象。Servlet使用重新構造得到的Call對象中的對象ID在服務管理器中確定具體的對象。

        接下來,Servlet在已經確定的對象上檢驗被調用方法的名字并調用方法。完成后,Servlet串行化該調用的返回值,在HTTP應答中把它發送給客戶程序。

        從上述討論中,我們可以發現一個有趣的問題:Apache SOAP如何知道串行化某種給定數據類型的方法?Apache SOAP通過一個類型注冊器(org.apache.soap.encoding.SOAPMappingRegistry),以及通過所有裝配器(marshaller)和反裝配器(marshaller)分別必須實現的串行化(org.apache.soap.util.xml.Serializer)和反串行化(org.apache.soap.util.xml.Deserialization)接口,實現Java數據類型和XML之間的裝配和反裝配。Apache SOAP提供了許多實現這些接口的內建的裝配器和反裝配器。例如,我們可以用org.apache.soap.encoding.soapenc.BeanSerializer類裝配和反裝配JavaBean。本文后面我將介紹如何使用這個類。

        對于Java基本數據類型(int,long,double,等等)及其對應的對象化形式(Integer,Long,Double,等等)來說,它們的串行化器和反串行化器已經預先在類型映射注冊器中注冊。因此,對于客戶程序來說,用Java基本數據類型及其對象形式作為方法參數是無縫的。然而,如果服務所要求的參數是一個JavaBean,則必須手工在類型映射注冊器中注冊BeanSerializer。服務永遠不會做任何額外的工作,最后客戶程序的負擔總是較多。在這個系列的第四篇文章中,我將介紹用動態代理構造的框架,它將使創建客戶程序和創建服務程序一樣簡單。

        2.2、部署HelloWorld服務
        部署Apache SOAP服務有兩種方法:使用Web界面的管理工具,或通過命令行進行部署。所有這兩種方法都可以部署服務,使服務可被客戶程序訪問。

        ■ 使用管理工具部署服務

        要使用管理工具,用瀏覽器打開http://localhost:8080/apache-soap/admin。瀏覽器將顯示出圖一所示的界面。點擊窗口左邊的Deploy按鈕,一個帶有許多輸入框的窗口就會出現。并非所有的輸入框現在都要用到。我將在用到這些輸入框的時候介紹它們的含義。由于本文無需用到所有的輸入框,所以我們將忽略部分輸入框的含義。但不用擔心,到第三篇文章結束時,我將介紹完所有的輸入框。

        ID輸入框用來設置對象ID;如前所述,SOAP基礎設施利用對象ID把RPC請求綁定到SOAP服務。我在前面已經提到,所有Apache SOAP服務必須有一個對象ID,這個對象ID在該服務器上部署的所有服務之間唯一。我通常使用“urn:&lt;UniqueServiceID&gt;”格式,其中UniqueServiceID是服務的唯一對象ID。在本例中,把ID設置成“urn:hello”。

        Scope輸入框用來定義響應調用請求的服務實例的生存范圍和時間。Scope可以是下列值之一:

      • page:服務實例一直有效,直至應答發送完畢或把請求傳遞給了另一個頁面——如果使用標準的部署機制,向前傳遞請求不太可能發生。
      • request:服務實例在請求處理期間一直有效,不管是否出現請求傳遞。
      • session:服務實例對于整個會話都有效。
      • application:服務實例被用于所有對服務的調用請求。
          Scope的值對安全有著重要的影響,記住這一點很重要。page和request值確保了連續調用之間的隔離。在另一個極端,application值意味著所有SOAP的用戶共享服務實例。細心的讀者可能已經注意到,JSP的<jsp:useBean>標記同樣要用到這些值。事實上,rpcrouter Servlet曾經就是一個JSP頁面,這也許是這些值被選用的原因。在本例中,我們把Scope的值設置成application。

          在Methods輸入框中,輸入用空白字符分隔的方法名字,這些方法名字指示出當前部署的服務上允許調用的方法。我們的服務示例只支持一個方法,即sayHelloTo()。

          把Provider Type設置成Java。它意味著服務用Java實現,而且你必須為Apache SOAP提供服務完整的類名。這個任務在Provider Class輸入框完成,我們把它設置成hello.HelloServer。由于sayHelloTo()方法不是靜態的,保持Static輸入框原來的值,即no。

          現在滾動到頁面的下方,點擊表單下面的Deploy按鈕(不是左邊的Deploy按鈕)。要驗證服務已經部署完畢,點擊左邊的List按鈕,這時列表所顯示的服務中應該包含一個urn:Hello服務。

          ■ 從命令行部署服務

          部署服務除了可以用Web界面的管理工具,還可以用命令行Java工具org.apache.soap.server.ServiceManagerClient,它是一個Apache SOAP附帶的類。這個類要求有兩個必不可少的參數,一個指向Apache SOAP路由Servlet(即rpcrouter)的URL,以及一個動作。這個動作可以是以下四者之一:deploy,undeploy,list,或query。根據指定動作的不同,有時候還要提供額外的參數。例如,如果動作是deploy,則必須提供XML部署描述器文件的名字。部署描述器文件應該包含Apache SOAP服務器成功部署服務所需要的全部信息。例如,描述HelloWorld部署細節的部署XML文件可以如下:

          <isd:service xmlns:isd="http://xml.apache.org/xml-soap/deployment"
          id="urn:Hello">
          <isd:provider type="java" scope="Application" methods="sayHelloTo">
          <isd:java class="hello.HelloServer" static="false"/>
          </isd:provider>
          </isd:service>


          上述XML代碼所包含的信息和我們在Web界面的管理工具中所輸入的信息一樣。接下來,輸入下面的命令,從命令行部署HelloWorld服務:

          java org.apache.soap.server.ServiceManagerClient
          http://localhost:8080/apache-soap/servlet/rpcrouter
          deploy DeploymentDescriptor.xml


          DeploymentDescriptor.xml是上面顯示的描述部署信息的XML文件名字。要驗證服務是否部署成功,輸入以下命令:

          java org.apache.soap.server.ServiceManagerClient
          http://localhost:8080/apache-soap/servlet/rpcrouter query urn:Hello


          這時,我們應該看到和DeploymentDescriptor.xml文件內一樣的XML。

          2.3、HelloWorld客戶程序
          編寫客戶程序要比編寫HelloWorld服務復雜得多。不過,你應該不會對此感到奇怪,因為前面已經提到,客戶程序(至少)必須負責設置Call對象,這需要不少工作。順便說一下,本系列文章的第四篇將介紹一個框架,這個框架以Java 2 1.3版新引入的動態代理類為基礎,使創建客戶程序和創建服務一樣簡單。

          Listing 1顯示了完整的客戶程序。接下來我們一步一步地仔細看看這個程序。這個程序需要一個必不可少的參數:程序要向他說Hello信息的用戶名字。

          Listing 1: Client.java

          package hello;

          import java.net.URL;
          import java.util.Vector;
          import org.apache.soap.SOAPException;
          import org.apache.soap.Constants;
          import org.apache.soap.Fault;
          import org.apache.soap.rpc.Call;
          import org.apache.soap.rpc.Parameter;
          import org.apache.soap.rpc.Response;

          public class Client
          {
          public static void main(String[] args) throws Exception
          {
          if(args.length == 0)
          {
          System.err.println("Usage: java hello.Client [SOAP-router-URL] ");
          System.exit (1);
          }

          try
          {
          URL url = null;
          String name = null;
          if(args.length == 2)
          {
          url = new URL(args[0]);
          name = args[1];
          }
          else
          {
          url = new URL("http://localhost:8080/apache-soap/servlet/rpcrouter");
          name = args[0];
          }

          // 構造Call對象
          Call call = new Call();
          call.setTargetObjectURI("urn:Hello");
          call.setMethodName("sayHelloTo");
          call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC);
          Vector params = new Vector();
          params.addElement(new Parameter("name", String.class, name, null));
          call.setParams(params);

          // 發出調用
          Response resp = null;
          try
          {
          resp = call.invoke(url, "");
          }
          catch( SOAPException e )
          {
          System.err.println("Caught SOAPException (" + e.getFaultCode() + "): " +
          e.getMessage());
          System.exit(-1);
          }

          // 檢查應答
          if( !resp.generatedFault() )
          {
          Parameter ret = resp.getReturnValue();
          Object value = ret.getValue();
          System.out.println(value);
          }
          else
          {
          Fault fault = resp.getFault();
          System.err.println("Generated fault: ");
          System.out.println (" Fault Code = " + fault.getFaultCode());
          System.out.println (" Fault String = " + fault.getFaultString());
          }
          }
          catch(Exception e)
          {
          e.printStackTrace();
          }
          }
          }


          客戶程序首先設置Call對象,它需要如下信息:

        • 被調用服務的對象ID,它通過Call對象的setTargetObjectURI()方法設置。本例的對象ID是urn:Hello。
        • 待調用方法的名字,它通過Call對象的setMethodName()方法設置。本例的方法名字是sayHelloTo()。
        • 參數的編碼方式,它通過Call對象的setEncodingStyleURI()方法設置。本例我們使用標準的SOAP編碼方式,這種編碼方式由名稱空間http://schemas.xmlsoap.org/soap/encoding/定義。
        • 方法調用的參數通過Call對象的setParams()方法設置。setParams()方法的參數是一個Java Vector(向量)。這個向量包含所有的參數,向量中索引為0的參數是被調用方法從左邊數起的第一個參數,索引為1的參數是被調用方法從左邊數起的第二個參數,依此類推。向量中的每一個元素都是一個org.apache.soap.rpc.Parameter的實例。Parameter構造函數要求指定參數的名字、Java類型和值,還有一個可選的編碼方式。如果指定了null編碼方式(正如本例所做的那樣),則默認使用Call對象的編碼方式。雖然每一個參數對應著一個名字,但這個名字可以設置成任何內容,Apache SOAP服務器調用方法時不會用到這個名字。因此,絕對有必要讓向量中參數的次序和被調用方法的參數次序一致。
            下面的代碼片斷顯示了客戶程序創建Call對象的過程:

            // 構造Call對象
            Call call = new Call();
            call.setTargetObjectURI("urn:Hello");
            call.setMethodName("sayHelloTo");
            call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC);
            Vector params = new Vector();
            params.addElement(new Parameter("name", String.class, name, null));
            call.setParams(params);


            現在,該是實際調用HelloWorld遠程服務所提供方法的時候了。為此,客戶程序調用了Call對象的invoke()方法,這個方法返回一個org.apache.soap.rpc.Response對象,如下所示:

            // 發出調用
            Response resp = null;
            try
            {
            resp = call.invoke(url, "");
            }
            catch( SOAPException e )
            {
            System.err.println("Caught SOAPException (" + e.getFaultCode() + "): " +
            e.getMessage());
            System.exit(-1);
            }


            接下來,客戶程序檢查Response對象。如果方法調用過程中出現了錯誤,generateFault()方法返回一個true值,客戶程序提取并顯示實際的錯誤信息:

            Fault fault = resp.getFault();
            System.err.println("Generated fault: ");
            System.out.println (" Fault Code = " + fault.getFaultCode());
            System.out.println (" Fault String = " + fault.getFaultString());


            如果方法調用成功,則客戶程序提取并顯示Hello信息:

            // 檢查應答
            if( !resp.generatedFault() )
            {
            Parameter ret = resp.getReturnValue();
            Object value = ret.getValue();
            System.out.println(value);
            }


            三、帶有JavaBean的HelloWorld實例
            如前所述,Apache SOAP提供了許多預先構造的串行化和反串行化方法,其中包括為利用Java Vector、Enumeration、數組、JavaBean作為參數和返回值而提供的串行化器和反串行化器。在這一部分,我將修改HelloWorld服務,通過一個JavaBean傳入接收Hello信息的用戶名。

            3.1、HelloWorld服務
            改寫后的HelloWorld服務完整代碼如下:

            package hello;
            public class HelloServer
            {
            public String sayHelloTo(String name)
            {
            System.out.println("sayHelloTo(String name)");
            return "Hello " + name + ", How are you doing?";
            }

            public String sayHelloTo(Name theName)
            {
            System.out.println("sayHelloTo(Name theName)");
            return "Hello " + theName.getName() + ", How are you doing?";
            }
            }


            服務的代碼仍舊很簡單,仍舊類似于不用JavaBean時的HelloWorld服務。不過,這意味著最復雜的工作都轉移到了客戶端。事實上,這個版本的服務與以前版本的唯一差別在于,現在出現了一個重載的sayHelloTo()方法。上面的代碼中重載后的方法用粗體字顯示。

            重載的方法需要一個對Name JavaBean的引用。Name JavaBean的定義如下:

            package hello;

            public class Name
            {
            private String name;
            public String getName()
            {
            return name;
            }
            public void setName(String name)
            {
            this.name = name;
            }
            }


            3.2、部署服務
            部署一個使用了JavaBean的服務時,需要為Apache SOAP服務器提供一些額外的信息。因此,現在部署服務的過程稍微復雜一點。

            ■ 使用管理工具部署服務

            要使用管理工具部署這個新版的HelloWorld服務,首先按照前面所介紹的步驟進行,但這一次不要點擊Deploy按鈕。現在,在Number of Mappings輸入框輸入1,它表示我們將給出一個映射(即Name JavaBean)的信息。緊接Mappings之下有一個表格,我們要用到這個表格的第一行。保留Encoding Style的值為SOAP,把NameSpace URI設置成對象的ID:在本例中,它是urn:Hello。接下來,把Local Part和Java Type輸入框設置成Name JavaBean的完整名字,即hello.Name。最后,把Java to XML Serializer和XML to Java Deserializer輸入框設置成org.apache.soap.encoding.soapenc.BeanSerializer,這是一個實現了Serializer和Deserializer接口的類,用來串行化和反串行化Name JavaBean。如果你用到了更多的JavaBean(比如還有一個Address Bean),則應該在這個表格中輸入其他Bean的信息,同時還應該更新Number of Mappings輸入框的值,使之反映出表格中實際被使用的行數。

            ■ 從命令行部署服務

            要從命令行進行部署,我們只需修改作為命令行參數傳入的XML部署描述器文件。修改后的XML文件如下所示:

            <isd:service xmlns:isd="http://xml.apache.org/xml-soap/deployment" id="urn:Hello">
            <isd:provider type="java" scope="Application" methods="sayHelloTo">
            <isd:java class="hello.HelloServer" static="false"/>
            </isd:provider>
            <isd:mappings>
            <isd:map encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
            xmlns:x="urn:Hello" qname="x:hello.Name"
            javaType="hello.Name"
            java2XMLClassName="org.apache.soap.encoding.soapenc.BeanSerializer"
            xml2JavaClassName="org.apache.soap.encoding.soapenc.BeanSerializer"/>
            </isd:mappings>
            </isd:service>


            正如在前一個例子中,這些XML代碼所包含的信息和通過Web界面的管理工具所提供的信息一樣。

            3.3、HelloWorld客戶程序
            和第一個例子一樣,客戶程序更復雜,也更令人感興趣。這里我不再仔細分析整個客戶程序,而是介紹兩個客戶程序版本的不同之處。由于調用方法的一個參數(在本例中,它是唯一的參數)是一個JavaBean,所以必須手工設置一個類型映射注冊項。這個任務通過如下步驟完成:先創建org.apache.soap.encoding.SOAPMappingRegistry類的一個實例,然后調用它的mapTypes()方法。正如mapTypes()方法名字所預示的,它用來注冊一個以前未知的類型,比如定制的JavaBean。mapTypes()方法的參數包括要使用的編碼方式、限定的JavaBean名字、類型的完整類名、串行化器和反串行化器。在本例中,執行串行化任務的是標準的Bean串行化器。限定的JavaBean名字包含一個元素的名字,包括它所屬的名稱空間。在本例中,Name JavaBean的限定名字由名稱空間URI(urn:Hello)和本地名字(hello.Name)結合構成。請看下面的代碼片斷:

            // 創建類型映射注冊器
            SOAPMappingRegistry smr = new SOAPMappingRegistry();
            BeanSerializer beanSer = new BeanSerializer();
            // 映射類型
            smr.mapTypes(Constants.NS_URI_SOAP_ENC,
            new QName("urn:Hello", "hello.Name"),hello.Name.class, beanSer, beanSer);


            接下來,客戶程序必須告訴Call對象使用新的注冊器而不是默認的注冊器。為此,我們要調用Call對象的setSOAPMappingRegistry()方法,如下所示:

            call.setSOAPMappingRegistry(smr);

            手工設置好類型映射注冊器之后,接下來還必須為Call對象設置參數。這一步驟可以按前面介紹的方法完成,不同之處在于,現在我們不再用字符串類型的名字作為參數,而是用JavaBean作為參數,如下所示:

            // 設置調用參數
            Vector params = new Vector();
            Name theName = new Name();
            theName.setName(name);
            params.addElement(new Parameter("name", hello.Name.class, theName, null));
            call.setParams(params);


            客戶程序剩下的部分和原來的版本一樣。Listing 3顯示了完整的客戶程序代碼:

            Listing 3: Client2.java

            package hello;

            import java.net.URL;
            import java.util.Vector;
            import org.apache.soap.SOAPException;
            import org.apache.soap.Constants;
            import org.apache.soap.Fault;
            import org.apache.soap.rpc.Call;
            import org.apache.soap.rpc.Parameter;
            import org.apache.soap.rpc.Response;
            import org.apache.soap.encoding.SOAPMappingRegistry;
            import org.apache.soap.encoding.soapenc.BeanSerializer;
            import org.apache.soap.util.xml.QName;

            public class Client2
            {
            public static void main(String[] args) throws Exception
            {
            if(args.length == 0)
            {
            System.err.println("Usage: java hello.Client [SOAP-router-URL] ");
            System.exit (1);
            }

            try
            {
            URL url = null;
            String name = null;
            if(args.length == 2)
            {
            url = new URL(args[0]);
            name = args[1];
            }
            else
            {
            url = new URL("http://localhost:8080/apache-soap/servlet/rpcrouter");
            name = args[0];
            }

            // 構造調用對象
            Call call = new Call();
            call.setTargetObjectURI("urn:Hello");
            call.setMethodName("sayHelloTo");
            call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC);

            // 創建類型映射注冊器
            SOAPMappingRegistry smr = new SOAPMappingRegistry();
            BeanSerializer beanSer = new BeanSerializer();
            // 映射類型
            smr.mapTypes(Constants.NS_URI_SOAP_ENC,
            new QName("urn:Hello", "hello.Name"),
            hello.Name.class, beanSer, beanSer);
            call.setSOAPMappingRegistry(smr);

            // 設置參數
            Vector params = new Vector();
            Name theName = new Name();
            theName.setName(name);
            params.addElement(new Parameter("name", hello.Name.class,
            theName, null));
            call.setParams(params);

            // 發出調用
            Response resp = null;
            try
            {
            resp = call.invoke(url, "");
            }
            catch( SOAPException e )
            {
            System.err.println("Caught SOAPException (" +
            e.getFaultCode() + "): " + e.getMessage());
            System.exit(-1);
            }

            // 檢查應答
            if( !resp.generatedFault() )
            {
            Parameter ret = resp.getReturnValue();
            Object value = ret.getValue();
            System.out.println(value);
            }
            else
            {
            Fault fault = resp.getFault();
            System.err.println("Generated fault: ");
            System.out.println (" Fault Code = " + fault.getFaultCode());
            System.out.println (" Fault String = " + fault.getFaultString());
            }
            }
            catch(Exception e)
            {
            e.printStackTrace();
            }
            }
            }


            四、編譯和運行程序
            現在整個程序的開發工作已經完成,該是運行它的時候了。不過,我們首先要編譯服務程序和客戶程序。

            創建一個hello目錄,把Client1.java、Client2.java和HelloServer.java復制到這個目錄。我把hello目錄放到了Apache SOAP的示例目錄(即E:\soap-2_0\samples)之下。編譯程序時,classpath中只需包含hello目錄的父目錄(即E:\soap-2_0\samples)、soap.jar和xerces.jar。我用下面的批命令編譯程序:

            set CLASSPATH=E:\soap-2_0\samples\;E:\soap-2_0\lib\soap.jar;E:\xerces-1_2_0\xerces.jar
            javac -d .. HelloServer.java Client.java Client2.java


            注意:從hello目錄執行這個批命令文件。

            要使用這個服務,除了部署它之外,還需要修改Web服務器的classpath,確保Web服務能夠找到hello.HelloServer類——對于本例,這是指把E:\soap-2_0\samples加入到Web服務器的classpath。對classpath進行必要的修改之后,重新啟動Web服務器。接下來就可以運行客戶程序了。下面是我運行hello.Client的批命令文件:

            set CLASSPATH=E:\soap-2_0\samples\;E:\soap-2_0\lib\soap.jar;E:\xerces-1_2_0\xerces.jar
            java hello.Client Tarak

            這里的classpath和編譯程序時用的classpath相同。

            最后,運行hello.Client2的批命令文件可以如下:

            set CLASSPATH=E:\soap-2_0\samples\;E:\soap-2_0\lib\soap.jar;E:\xerces-1_2_0\xerces.jar
            java hello.Client2 Tarak

            觀察Web服務器的控制臺窗口,看看在運行兩個不同的客戶程序時,HelloWorld服務的哪些方法正在被調用。

            ■ 結束語
            在這篇文章中,我介紹了如何用Apache SOAP實現來創建簡單的基于SOAP的服務。在SOAP實現方面,另一個重要的競爭者是Microsoft。遺憾的是,“純”Java開發者在使用Microsoft實現的時候會有一段艱苦的時光,因為它的實現包含了COM對象。

            在下一篇文章中,我將介紹Apache SOAP支持的另一種創建服務的方式:使用JavaScript之類的腳本語言,而不是Java。另外,我還要介紹一個很不錯的JavaScript引擎,即Rhino。


          • 禮物 2007-11-09 22:03 發表評論
            ]]>
            SOAP凈化有線協議(一):SOAP基礎知識http://www.aygfsteel.com/libin2722/articles/159454.html禮物禮物Fri, 09 Nov 2007 13:59:00 GMThttp://www.aygfsteel.com/libin2722/articles/159454.htmlhttp://www.aygfsteel.com/libin2722/comments/159454.htmlhttp://www.aygfsteel.com/libin2722/articles/159454.html#Feedback0http://www.aygfsteel.com/libin2722/comments/commentRss/159454.htmlhttp://www.aygfsteel.com/libin2722/services/trackbacks/159454.html
            SOAP的全稱是Simple Object Access Protocol,即簡單對象訪問協議。簡單地說,SOAP是一種有線協議,類似于CORBA的IIOP、DCOM的ORPC或Java遠程方法調用的Java遠程方法協議(Java Remote Method Protocol,JRMP)。你也許會懷疑,既然已經有了那么多有線協議,為什么我們還需要另外一種?事實上,這不正好導致前面所討論的問題嗎?這些問題都有道理,但是,SOAP和其他有線協議有所不同。

            我們來分析一下:

            • IIOP、ORPC和JRMP都是二進制協議,而SOAP則是一種使用XML的以文本為基礎的協議。利用XML進行數據編碼為SOAP帶來一些獨一無二的功能。例如,調試以SOAP為基礎的應用程序更容易,因為閱讀XML要比閱讀二進制數據容易得多。另外,由于所有的SOAP消息都是文本格式,和IIOP、ORPC或者JRMP相比,SOAP更容易和防火墻協作。
            • SOAP協議以非供應商私有的協議為基礎,即XML、HTTP和Simple Mail Transfer Protocol(SMTP),所有供應商都可以使用SOAP協議。例如,Microsoft和各個CORBA ORB供應商(例如Iona)一樣,已經承諾支持SOAP。IBM在創建SOAP協議的過程中起到了重要的作用,它也為Java程序員創建了一個優秀的SOAP工具包。該公司已經把工具包捐贈給Apache Software Foundation的XML Project,后者以該軟件包為基礎,構造出了Apache-SOAP實現。這個實現在Apache許可之下免費提供給用戶。再返回來看本文開頭提出的問題,如果DCOM使用SOAP,ORB供應商也使用了SOAP,那么,COM/CORBA協同操作中出現的問題將變得不值一提。

            SOAP決不只是一個漂亮的口號,它是一種即將深入滲透到未來分布式計算的技術。人們希望,SOAP結合其他技術,比如UDDI(Universal Discovery Description, and Integration)和WSDL(Web Services Description Language),在Web服務這一概念的支持下,改變未來商業應用跨越Web進行通信的方法。我甚至無法充分地表達出在開發者的工具包中加上SOAP知識的重要程度。這是一個關于SOAP的系列文章,總共四篇。這是第一篇,介紹一些基礎知識。我們將從SOAP這一思想的構思說起。

            一、SOAP簡介
            如前所述,SOAP用XML作為數據編碼格式。用XML作為數據編碼格式并非SOAP的原創,實際上這是一種相當自然的選擇。XML-RPC和ebXML也同樣使用XML。要了解這方面的更多信息,請參見本文最后的“參考資源”。

            請考慮下面的Java接口:

            Listing 1

            public interface Hello
            {
            public String sayHelloTo(String name);
            }

            客戶程序在調用sayHelloTo()方法時提供了一個名字,它希望從服務器接收到一則個性化的“Hello”信息。現在,假定RMI、CORBA和DCOM都不存在,開發者必須負責串行化方法調用,并把消息發送給遠程機器。幾乎所有的人都會說“這該使用XML”,我同意。因此,讓我們先從對服務器的請求格式開始。假設要模擬sayHelloTo("John")調用,我打算發送的請求是:

            Listing 2

            <?xml version="1.0"?>
            <Hello>
            <sayHelloTo>
            <name>John</name>
            </sayHelloTo>
            </Hello>

            在這里,我把接口的名字作為根結點。另外,我還把方法名字和參數名字都當作節點。接下來,我們要把這個請求發送給服務器。我們不創建自己的TCP/IP消息,而是使用HTTP。因此,下面的步驟應該是把請求封裝成HTTP POST請求格式,然后把它發送給服務器。實際創建該HTTP POST請求的詳細過程在本文后面介紹,現在,我們先假定它已經創建完畢。服務器接收到了這個請求,解碼XML,然后再以XML格式向客戶程序發送應答。假設應答內容如下:

            Listing 3

            <?xml version="1.0"?>
            <Hello>
            <sayHelloToResponse>
            <message>Hello John, How are you?</message>
            </sayHelloToResponse>
            </Hello>

            根節點仍然是接口的名字Hello。但這一次,原來對應著方法的節點名字不再是sayHelloTo,而是方法的名字加上“Response”字符串。客戶程序知道自己調用了哪一個方法,要找出被調用方法的返回值,它只需查看名字為方法名字加上“Response”字符串的元素。

            以上就是SOAP的根本思路。Listing 4顯示了同一請求用SOAP XML編碼之后的結果:

            Listing 4

            <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance" xmlns:xsd="http://www.w3.org/1999/XMLSchema">
            <SOAP-ENV:Header>
            </SOAP-ENV:Header>
            <SOAP-ENV:Body>
            <ns1:sayHelloTo
            xmlns:ns1="Hello"
            SOAP-ENV:encodingStyle="
            http://schemas.xmlsoap.org/soap/encoding/">
            <name xsi:type="xsd:string">John</name>
            </ns1:sayHelloTo>
            </SOAP-ENV:Body>
            </SOAP-ENV:Envelope>

            看起來稍微復雜了一點,對吧?實際上,它和我們前面編寫的請求類似,只是略微擴展了一些東西。首先,注意SOAP文檔通過一個Envelope(根節點)、一個Header區、一個Body區,整潔地組織到一起。Header區用來封裝那些與方法本身無直接關系的數據,提供環境方面的信息,比如事務ID和安全信息。Body區包含面向方法本身的信息。在Listing 2中,我們自己編寫的XML只包含一個Body區。

            第二,注意Listing 4大量地應用了XML名稱空間。SOAP-ENV映射到名稱空間http://schemas.xmlsoap.org/soap/envelope/,xsi映射到http://www.w3.org/1999/XMLSchema-instance,而xsd映射到http://www.w3.org/1999/XMLSchema。這三者是所有SOAP文檔都擁有的標準名稱空間。

            最后,在Listing 4中,接口名稱(即Hello)不再象在Listing 2中那樣成為節點的名字;相反,它引用了一個名稱空間nsl。另外,參數的類型信息也隨同參數的值一起發送給了服務器。注意信封(Envelope)encodingStyle屬性的值。這個屬性值設置成了http://schemas.xmlsoap.org/soap/encoding/。這個值告訴服務器用來編碼(即串行化)方法的編碼方式;服務器需要這個信息,以便正確地解除方法的串行化。對于服務器來說,SOAP文檔的自我描述能力是相當完善的。

            對于上面的SOAP請求,服務器的應答如下:

            Listing 5

            <SOAP-ENV:Envelope
            xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
            xmlns:xsi="http://www.w3.org/1999/XMLSchema-nstance"
            xmlns:xsd="http://www.w3.org/1999/XMLSchema">
            <SOAP-ENV:Body>
            <ns1:sayHelloToResponse
            xmlns:ns1="Hello"
            SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
            <return xsi:type="xsd:string">Hello John, How are you doing?</return>
            </ns1:sayHelloToResponse>
            </SOAP-ENV:Body>
            </SOAP-ENV:Envelope>

            Listing 5與Listing 4的請求消息類似。在上面的代碼中,返回值(即個性化的“Hello”消息)包含在Body區。SOAP消息文檔的格式非常靈活。例如,編碼方式不固定,而是由客戶程序指定。只要是客戶程序和服務器都認可的編碼方式,可以是任何合法的XML文檔。

            另外,分離調用環境信息意味著方法本身并不關心這類信息。在當前的市場上,主流應用服務器都遵從這一理念。早先,我曾經指出環境信息可以包含事務和安全方面的信息。事實上,環境可以涵蓋幾乎所有的東西。下面是一個SOAP消息頭的例子,它帶有一些事務方面的信息:

            Listing 6

            <SOAP-ENV:Header>
            <t:Transaction xmlns:t="some-URI" SOAP-ENV:mustUnderstand="1">
            5
            </t:Transaction>
            </SOAP-ENV:Header>

            名稱空間t映射到了與特定應用有關的URI。這里的5表示的是該方法從屬于其中的事務ID。注意SOAP信封mustUnderstand屬性的應用。這個屬性被設置成了1,它表示服務器要么理解并按照要求處理該事務請求,要么表示無法處理該請求;這是SOAP規范所要求的。

            二、錯誤處理
            使用SOAP并不意味著任何時候所有的請求都會獲得成功。許多地方可能會出現差錯。例如,服務器可能無法訪問某個關鍵性的資源(比如數據庫),因而無法順利地處理請求。

            讓我們返回“Hello”實例,為它加上一個假想的約束,即“在星期二向別人說Hello不合法。”因此,星期二的時候,即使發送給服務器的請求是合法的,服務器也會把一個錯誤信息返回給客戶端。應答內容將如下所示:

            Listing 7

            <SOAP-ENV:Envelope xmlns:SOAP-ENV="
            http://schemas.xmlsoap.org/soap/envelope/">
            <SOAP-ENV:Body>
            <SOAP-ENV:Fault>
            <faultcode>SOAP-ENV:Server</faultcode>
            <faultstring>Server Error</faultstring>
            <detail>
            <e:myfaultdetails xmlns:e="Hello">
            <message>
            Sorry, my silly constraint says that I cannot say hello on Tuesday.
            </message>
            <errorcode>
            1001
            </errorcode>
            </e:myfaultdetails>
            </detail>
            </SOAP-ENV:Fault>
            </SOAP-ENV:Body>
            </SOAP-ENV:Envelope>

            我們來分析一下http://schemas.xmlsoap.org/soap/envelope/名稱空間定義的Fault元素。Fault元素總是Body元素的直接子元素,所有的SOAP服務器必須始終通過該元素報告所有錯誤情況。Fault元素必須包含faultcode和faultstring元素,不能有例外。faultcode是一個能夠標識問題的代碼;客戶程序按照SOAP規范的要求利用faultcode進行算法處理。SOAP規范定義了一小組錯誤代碼供用戶使用。另一方面,faultstring是供人類閱讀的錯誤信息。

            Listing 7的代碼還包含了一個detail元素。由于錯誤在處理SOAP消息的Body區時出現,detail元素必須出現。正如你將在本文后面看到的,如果錯誤在處理Header區時出現,detail元素不應出現。在Listing 7中,應用利用detail元素提供當前錯誤更詳細、更自然的解釋,即星期二不允許說Hello。SOAP還提供另外一個面向具體應用的錯誤代碼,即半可選的faultfactor元素,但上面的錯誤信息中沒有顯示這個元素。之所以稱這個元素是半可選的,是因為如果錯誤消息不是由請求最終處理點的服務器發送,即由一個中間服務器發送,則錯誤消息必須包含該元素。SOAP對faultcode元素不應出現的情形沒有作任何規定。

            在Listing 7中,錯誤起源于方法調用本身,處理該方法的應用導致了這個錯誤。現在,我們來看一下另一種類型的錯誤,這種錯誤由于服務器不能處理請求頭信息而導致。舉例來說,假設所有的Hello消息必須在一個事務環境之內生成,則請求類似于:

            Listing 8

            <SOAP-ENV:Envelope
            xmlns:SOAP-ENV="
            http://schemas.xmlsoap.org/soap/envelope/"
            xmlns:xsi="
            http://www.w3.org/1999/XMLSchema-instance"
            xmlns:xsd="http://www.w3.org/1999/XMLSchema">
            <SOAP-ENV:Header>
            <t:Transaction xmlns:t="some-URI"
            SOAP-ENV:mustUnderstand="1">
            5
            </t:Transaction>
            </SOAP-ENV:Header>
            <SOAP-ENV:Body>
            <ns1:sayHelloTo
            xmlns:ns1="Hello"
            SOAP-ENV:encodingStyle="
            http://schemas.xmlsoap.org/soap/encoding/">
            <name xsi:type="xsd:string">Tarak</name>
            </ns1:sayHelloTo>
            </SOAP-ENV:Body>
            </SOAP-ENV:Envelope>


            上面消息的Header區包含一個transaction元素,它指定了方法調用必須從屬于其中的事務編號。這里我說“必須”是因為transaction元素使用了mustUnderstand屬性。如前所述,SOAP服務器要么遵照屬性的指示處理請求,要么聲明不能處理請求。假定SOAP服務器不能處理,它必須返回一個錯誤信息。這時的應答應該類似于:

            Listing 9

            <SOAP-ENV:Envelope xmlns:SOAP-ENV="
            http://schemas.xmlsoap.org/soap/envelope/">
            <SOAP-ENV:Body>
            <SOAP-ENV:Fault>
            <faultcode>SOAP-ENV:MustUnderstand</faultcode>
            <faultstring>SOAP Must Understand
            Error</faultstring>
            </SOAP-ENV:Fault>
            </SOAP-ENV:Body>
            </SOAP-ENV:Envelope>


            上面的代碼類似Listing 7顯示的錯誤信息。但應該注意的是,detail元素不再出現。正如我在前面指出的:SOAP規范規定,如果錯誤在處理Header區的時候出現,則錯誤消息中不應包含detail元素。事實上,我們可以根據detail元素是否出現,迅速判定錯誤是在處理Body區還是在處理Header區時出現。

            三、SOAP與HTTP
            在第一個例子中,我通過HTTP把定制的XML請求發送給服務器,但沒有詳細介紹這么做涉及到了哪些操作。現在我們回過頭來看那個問題。怎樣才能把一個SOAP請求(而不是定制的XML)通過HTTP發送給服務器?SOAP很自然地遵循了HTTP的請求/應答消息模型。這個模型在HTTP請求中提供SOAP請求參數,在HTTP應答中提供SOAP應答參數。實際上,SOAP 1.0特別指明HTTP作為它的傳輸協議。SOAP 1.1略有放松。雖然SOAP 1.1仍舊可以使用HTTP,但它也可以使用其他協議,比如SMTP。在這個系列的文章中,我只討論通過HTTP使用SOAP的情形。

            讓我們返回Hello示例。如果我們通過HTTP把SOAP請求發送給服務器,則代碼應該類似于:

            Listing 10

            POST http://www.SmartHello.com/HelloApplication HTTP/1.0
            Content-Type: text/xml; charset="utf-8"
            Content-Length: 587
            SOAPAction: "http://www.SmartHello.com/HelloApplication#sayHelloTo"
            <SOAP-ENV:Envelope
            xmlns:SOAP-ENV="
            http://schemas.xmlsoap.org/soap/envelope/"
            xmlns:xsi="
            http://www.w3.org/1999/XMLSchema-instance"
            xmlns:xsd="http://www.w3.org/1999/XMLSchema">
            <SOAP-ENV:Header>
            </SOAP-ENV:Header>
            <SOAP-ENV:Body>
            <ns1:sayHelloTo
            xmlns:ns1="Hello"
            SOAP-ENV:encodingStyle="
            http://schemas.xmlsoap.org/soap/encoding/">
            <name xsi:type="xsd:string">Tarak</name>
            </ns1:sayHelloTo>
            </SOAP-ENV:Body>
            </SOAP-ENV:Envelope>

             


            Listing 10代表的SOAP請求與Listing 4的請求基本相同,但Listing 10的開頭加入了一些HTTP特有的代碼。第一行代碼表明這是一個遵循HTTP 1.1規范的POST請求,POST的目標是http://www.SmartHello.com/HelloApplication。下一行指示內容的類型,在HTTP消息中包含SOAP實體時,內容類型必須是text/xml。Content-Length指明了POST請求有效載荷的長度。

            第四行是SOAP特有的,而且它是必不可少的。SOAPAction HTTP請求頭指明了SOAP HTTP請求的目標,它的值是一個標識目標的URI。SOAP不對該URI的格式作任何限制,實際上,這個URI甚至不必對應某個實際的位置。

            SOAPAction的一個可能的應用是,防火墻檢查該請求頭的值,決定是否允許請求通過防火墻

            一旦服務器處理完請求,它將向客戶返回一個應答。應答的內容如Listing 11所示(假設沒有出現錯誤):

            Listing 11

            HTTP/1.0 200 OK
            Content-Type: text/xml; charset="utf-8"
            Content-Length: 615
            <SOAP-ENV:Envelope
            xmlns:SOAP-ENV="
            http://schemas.xmlsoap.org/soap/envelope/"
            xmlns:xsi="
            http://www.w3.org/1999/XMLSchema-instance"
            xmlns:xsd="http://www.w3.org/1999/XMLSchema">
            <SOAP-ENV:Body>
            <ns1:sayHelloToResponse
            xmlns:ns1="Hello"
            SOAP-ENV:encodingStyle="
            http://schemas.xmlsoap.org/soap/encoding/">
            <return xsi:type="xsd:string">Hello John, How are
            you doing?</return>
            </ns1:sayHelloToResponse>
            </SOAP-ENV:Body>
            </SOAP-ENV:Envelope>

             

             


            這個SOAP應答與Listing 5所顯示的一樣,但前面加上了一些HTTP特有的代碼。由于沒有出現錯誤,第一行代碼顯示應答狀態是200。在HTTP協議中,200應答狀態代碼表示“一切正常”。如果在處理SOAP消息(Header區或者Body區)的時候出現了任何錯誤,則返回的狀態代碼將是500。在HTTP中,500狀態代碼表示“internal server error”。此時,上述SOAP應答的第一行代碼將是:

            HTTP 500 Internal Server Error

            四、HTTP擴充框架
            許多應用對服務的需求超過了傳統HTTP提供的服務。其結果就是,這類應用擴充了傳統的HTTP協議。然而,這種擴充是應用本身私有的。HTTP擴充框架試圖確立一個通用的HTTP擴充機制,從而解決這個問題。HTTP擴充框架的擴充之一是增加了一個M-POST方法,其中的M表示Mandatory(必須遵循的,強制的)。如果一個HTTP請求包含至少一個強制的擴充聲明,那么這個請求就稱為強制的請求。引入強制的擴充聲明通過Man或者C-Man頭進行。強制請求的請求方法名字必須帶有“M-”前綴,例如,強制的POST方法稱為M-POST。

            SOAP 1.0要求客戶程序首先發送一個HTTP POST請求,只有當服務器返回錯誤510時才發送M-POST請求。SOAP 1.1不再對客戶作這種限制,也就是說,SOAP 1.1允許客戶從發送任何一種類型的請求開始。下面的請求就是迄今為止我們一直在討論的那個請求,但它現在是M-POST格式:

            Listing 12

            M-POST http://www.SmartHello.com/HelloApplication HTTP/1.1
            Content-Type: text/xml; charset="utf-8"
            Content-Length: 587
            Man: "http://schemas.xmlsoap.org/soap/envelope/"; ns=01
            01-SOAPAction: "http://www.SmartHello.com/HelloApplication#sayHelloTo"
            <SOAP-ENV:Envelope
            xmlns:SOAP-ENV="
            http://schemas.xmlsoap.org/soap/envelope/"
            xmlns:xsi="
            http://www.w3.org/1999/XMLSchema-instance"
            xmlns:xsd="http://www.w3.org/1999/XMLSchema">
            <SOAP-ENV:Header>
            </SOAP-ENV:Header>
            <SOAP-ENV:Body>
            <ns1:sayHelloTo
            xmlns:ns1="Hello"
            SOAP-ENV:encodingStyle="
            http://schemas.xmlsoap.org/soap/encoding/">
            <name xsi:type="xsd:string">Tarak</name>
            </ns1:sayHelloTo>
            </SOAP-ENV:Body>
            </SOAP-ENV:Envelope>


            對于實際的SOAP消息來說,Listing 12和Listing 10沒有什么不同。但請求頭中有一些不同的地方,例如,現在我們發出的不是POST請求,而是一個M-POST請求。正如前面所介紹的,象M-POST這樣的強制請求至少有一個強制擴充聲明。這里我們就有一個:Man域描述了一個強制性的端到端擴充聲明,把頭前綴01映射到了名稱空間http://schemas.xmlsoap.org/soap/envelope/。請注意這個前綴關聯到SOAPAction域的方式。

            一旦服務器處理完該請求,它將返回一個應答給客戶。應答內容類如(假設沒有出現錯誤):

            Listing 13

            HTTP/1.0 200 OK
            Ext:
            Content-Type: text/xml; charset="utf-8"
            Content-Length: 615
            <SOAP-ENV:Envelope
            xmlns:SOAP-ENV="
            http://schemas.xmlsoap.org/soap/envelope/"
            xmlns:xsi="
            http://www.w3.org/1999/XMLSchema-instance"
            xmlns:xsd="http://www.w3.org/1999/XMLSchema">
            <SOAP-ENV:Body>
            <ns1:sayHelloToResponse
            xmlns:ns1="Hello"
            SOAP-ENV:encodingStyle="
            http://schemas.xmlsoap.org/soap/encoding/">
            <return xsi:type="xsd:string">Hello John, How are
            you doing?</return>
            </ns1:sayHelloToResponse>
            </SOAP-ENV:Body>
            </SOAP-ENV:Envelope>


            同樣地,Listing 13顯示的應答類似于對普通POST請求的應答(如Listing 11所示),兩者的不同之處在于Ext域。

            在通過HTTP使用SOAP的過程中,我們欣喜地看到,實際的SOAP消息(SOAP信封和它里面的所有內容)總是保持不變,就如消息尚未加載HTTP協議時一樣。根據這一事實可以推斷出,HTTP不是能夠與SOAP協作的唯一協議。例如,SOAP可以方便地和SMTP協議或者其他定制的私有協議一起運行。唯一的要求是兩者——客戶端和服務器端——都理解該協議。

            五、SOAP的特點:簡單
            至此為止,我們討論了SOAP定義的方方面面,但有許多領域的問題SOAP沒有定義。SOAP規范的創立者明確地排除了一些關系密切的領域,比如構造對象模型,還有其他許多已經確立的標準。

            造成這種現象的原因可以從分析SOAP的目標理解。SOAP的目標除了擴展性之外,另一個主要的設計目標是簡單。為了保持SOAP簡單,SOAP規范的創立者決定,只定義那些對于創建一個輕型協議來說絕對必須的東西。例如,SOAP沒有定義/指定任何有關分布式垃圾收集、類型安全或版本控制、雙向HTTP通信、消息盒(Message-box)運輸或管道處理、對象激活等方面的內容。SOAP的目標就是成為一種簡單的協議——一種在任何操作系統上,單個開發者能夠用任何語言化數天時間實現的協議。考慮到這一點,SOAP在許多方面沒有作出明確定義實際上是一件好事,因為在構造分布式系統時,所有現有的技術都可以方便地采用SOAP,即使不同技術之間的差異象CORBA和DCOM的差異那樣明顯。

            ■ 結束語
            在這篇文章中,我介紹了SOAP的一些基本概念,以及它之所以如此設計的一些原因。然而,相對于SOAP這座冰山來說,這只是它的一角。要查看有關SOAP的更多信息,請查閱參考資源部分給出的SOAP規范鏈接。隨著本系列文章的展開,我將在這里介紹有關SOAP規范所有你必須了解的知識。

            在第二部分中,我將介紹Apache的SOAP實現。我們將使用該實現,創建一個利用SOAP作為有線協議的簡單分布式應用

            禮物 2007-11-09 21:59 發表評論
            ]]>
            主站蜘蛛池模板: 南昌市| 日喀则市| 瑞丽市| 德保县| 茶陵县| 长宁区| 冀州市| 盐亭县| 石林| 乐陵市| 龙胜| 山西省| 宜宾县| 阿合奇县| 都安| 玛沁县| 西林县| 毕节市| 丹江口市| 惠州市| 陈巴尔虎旗| 临武县| 三门县| 榆树市| 韩城市| 饶平县| 闵行区| 吉木萨尔县| 大石桥市| 巴马| 漳州市| 措勤县| 新巴尔虎右旗| 旅游| 建水县| 皮山县| 长宁区| 广水市| 甘孜县| 古浪县| 栖霞市|