簡(jiǎn)單對(duì)象訪問(wèn)協(xié)議(SOAP)初級(jí)指南
2024-07-21 02:23:52
供稿:網(wǎng)友
總結(jié):
(本文假設(shè)讀者對(duì)com和xml技術(shù)已經(jīng)很熟悉。)
soap(simple object access protocal) 技術(shù)有助于實(shí)現(xiàn)大量異構(gòu)程序和平臺(tái)之間的互操作性,從而使存在的應(yīng)用能夠被廣泛的用戶所訪問(wèn)。soap是把成熟的基于http的web技術(shù)與xml的靈活性和可擴(kuò)展性組合在了一起。
這篇文章帶你全面回顧對(duì)象遠(yuǎn)程進(jìn)程調(diào)用(orpc)技術(shù)的歷程,以幫助你理解soap技術(shù)的基礎(chǔ),以及它克服存在技術(shù)(如corba和dcom)的許多缺陷的方法。隨后講述詳細(xì)的soap編碼規(guī)則,并把焦點(diǎn)放在soap是怎樣映射到存在的orpc概念上的。
引言:
當(dāng)我在1984年開(kāi)始把計(jì)算作為我的職業(yè)的時(shí)候,大多數(shù)程序員并不關(guān)心網(wǎng)絡(luò)協(xié)議。但是在九十年代網(wǎng)絡(luò)變得無(wú)所不在,現(xiàn)在如果有誰(shuí)使用計(jì)算機(jī)卻不使用某種形式網(wǎng)絡(luò)連接是很難以想象的。今天,一般的程序員對(duì)建立可擴(kuò)展的分布式應(yīng)用表現(xiàn)出更大的興趣,而不再只是關(guān)注于用mfc實(shí)現(xiàn)個(gè)性化的可浮動(dòng)半透明非矩形的coolbars了。
程序員通常喜歡用編程模型來(lái)思考問(wèn)題,而很少考慮網(wǎng)絡(luò)協(xié)議。盡管這樣做通常是很好的,但在這篇文章中我將討論的soap是一個(gè)沒(méi)有明顯的編程模型的網(wǎng)絡(luò)協(xié)議。這并不意味著soap的體系結(jié)構(gòu)從根本上會(huì)改變你編程的方式。相反,soap的一個(gè)主要目標(biāo)是使存在的應(yīng)用能被更廣泛的用戶所使用。為了實(shí)現(xiàn)這個(gè)目的,沒(méi)有任何soap api或soap 對(duì)象請(qǐng)求代理(soap orb),soap是假設(shè)你將使用盡可能多的存在的技術(shù)。幾個(gè)主要的corba廠商已經(jīng)承諾在他們的orb產(chǎn)品中支持soap協(xié)議。微軟也承諾在將來(lái)的com版本中支持soap。developmentor已經(jīng)開(kāi)發(fā)了參考實(shí)現(xiàn),它使得在任何平臺(tái)上的任何java或perl程序員都可以使用soap。
在soap后面的指導(dǎo)理念是“它是第一個(gè)沒(méi)有發(fā)明任何新技術(shù)的技術(shù)”。soap采用了已經(jīng)廣泛使用的兩個(gè)協(xié)議:http和xml。http用于實(shí)現(xiàn)soap的rpc風(fēng)格的傳輸,而xml是它的編碼模式。采用幾行代碼和一個(gè)xml解析器,http服務(wù)器(如ms的iis或apache)立刻成為了soap的orbs。 因?yàn)槟壳俺^(guò)一半的web服務(wù)器采用iis或apache, soap將會(huì)從這兩個(gè)產(chǎn)品的廣泛而可靠的使用中獲取利益。這并不意味著所有的soap請(qǐng)求必須通過(guò)web服務(wù)器來(lái)路由,傳統(tǒng)的web 服務(wù)器只是分派soap請(qǐng)求的一種方式。因此web服務(wù)如iis或apache對(duì)建立soap使能的應(yīng)用是充分的,但決不是必要的。
正如這篇文章將要描述的,soap簡(jiǎn)單地用xml來(lái)編碼http的傳輸內(nèi)容。soap最常用的應(yīng)用是作為一個(gè)rpc協(xié)議。為了理解soap怎樣工作,有必要簡(jiǎn)要回顧一下rpc協(xié)議的歷史。
rpcs的歷史
建立分布式應(yīng)用的兩個(gè)主要通信模型是消息傳送(經(jīng)常與隊(duì)列組合在一起)和請(qǐng)求/響應(yīng)。消息傳遞系統(tǒng)允許通信任何一方在任何時(shí)間發(fā)送消息。請(qǐng)求/響應(yīng)協(xié)議把通信模式限制在請(qǐng)求/響應(yīng)的雙方。基于消息的應(yīng)用強(qiáng)烈地意識(shí)到它們正在與外部的并行進(jìn)程進(jìn)行通信,并且需要一個(gè)顯式的設(shè)計(jì)風(fēng)格。基于請(qǐng)求/響應(yīng)的應(yīng)用更象一個(gè)單進(jìn)程的應(yīng)用,因?yàn)榘l(fā)送請(qǐng)求的應(yīng)用或多或少被阻塞直至收到來(lái)自另一個(gè)進(jìn)程的響應(yīng)。這使得請(qǐng)求/響應(yīng)通信自然地適合于rpc應(yīng)用。
盡管消息通信和請(qǐng)求/響應(yīng)各有他們的優(yōu)點(diǎn),他們都是可以用對(duì)方來(lái)實(shí)現(xiàn)的。消息系統(tǒng)可以用較底層的請(qǐng)求/響應(yīng)協(xié)議來(lái)建立。如微軟的message queue server (msmq)內(nèi)部采用了dce rpc來(lái)建立大多數(shù)的控制邏輯。rpc系統(tǒng)也可以采用較底層的消息系統(tǒng)來(lái)建立。msmq提供的關(guān)聯(lián) id正是為了這個(gè)目的。不管評(píng)價(jià)如何,大多數(shù)的應(yīng)用仍趨向于使用rpc協(xié)議,因?yàn)樗鼈儚V泛的使用,它們更簡(jiǎn)單的設(shè)計(jì),以及更自然的到傳統(tǒng)的編程技術(shù)的映射。
在八十年代,兩個(gè)主要的rpc協(xié)議是sun rpc 和dce rpc。最流行的sun rpc應(yīng)用是大多數(shù)unix系統(tǒng)所使用的network file system (nfs)。最流行的dce rpc應(yīng)用則是windows nt?,它采用dce rpc 協(xié)議來(lái)實(shí)現(xiàn)許多系統(tǒng)服務(wù)。這兩個(gè)協(xié)議被證明適用于很大范圍的應(yīng)用。但是,在八十年代末期,面向?qū)ο蠹夹g(shù)的風(fēng)靡使軟件界沉迷于在面向?qū)ο笳Z(yǔ)言和基于rpc的通信之間建立一個(gè)紐帶。
在九十年代產(chǎn)生的對(duì)象rpc (orpc) 協(xié)議正是試圖把面向?qū)ο蠛途W(wǎng)絡(luò)協(xié)議聯(lián)系起來(lái)。orpc 和 rpc 協(xié)議的主要不同是orpc代碼化了從通信終端到語(yǔ)言級(jí)對(duì)象的映射。在每個(gè)orpc請(qǐng)求的頭中都有一個(gè)cookie,服務(wù)器端的程序能用它來(lái)定位在服務(wù)器進(jìn)程中的目標(biāo)對(duì)象。通常這個(gè)cookie只是一個(gè)對(duì)數(shù)組的索引,但其它技術(shù)也經(jīng)常被使用,如用符號(hào)名作為hash表的鍵。
圖1 orpc請(qǐng)求與響應(yīng)
圖1表示一個(gè)典型的orpc請(qǐng)求和響應(yīng)。有幾個(gè)請(qǐng)求頭組件被服務(wù)器端的處理程序用于分發(fā)調(diào)用。對(duì)象端點(diǎn)id被用于定位在服務(wù)器進(jìn)程中目標(biāo)對(duì)象。接口標(biāo)識(shí)符和方法標(biāo)識(shí)符用于決定在目標(biāo)對(duì)象中哪一個(gè)方法被調(diào)用。傳輸體用于傳遞請(qǐng)求中的[in]和[in,out]參數(shù)的值(在響應(yīng)中是[out]和[in,out])。要注意的是任選的協(xié)議擴(kuò)展可以出現(xiàn)在頭文件和傳輸體之間。這是在協(xié)議設(shè)計(jì)中的慣例,因?yàn)樗试S新的服務(wù)搭載在orpc的請(qǐng)求和服務(wù)上。大多數(shù)orpc系統(tǒng)用這個(gè)區(qū)域傳遞附加的上下文信息(如事務(wù)信息和因果關(guān)系標(biāo)識(shí)符)。
目前兩個(gè)主要的oprc協(xié)議是dcom 和 corba的 internet inter-orb protocol (iiop) 或更一般的general inter-orb protocol (giop)。dcom和iiop/giop的請(qǐng)求格式非常相似。兩個(gè)協(xié)議都用一個(gè)對(duì)象端點(diǎn)id來(lái)確定目標(biāo)對(duì)象,用方法標(biāo)識(shí)符來(lái)決定調(diào)用哪個(gè)方法。
這兩個(gè)協(xié)議主要有兩點(diǎn)不同:主要的一點(diǎn)不同是采用iiop/giop時(shí),接口標(biāo)識(shí)符是隱含的,因?yàn)橐粋€(gè)給定的corba對(duì)象只實(shí)現(xiàn)一個(gè)接口(盡管omg當(dāng)前正在進(jìn)行每個(gè)對(duì)象有多個(gè)接口支持的標(biāo)準(zhǔn)化工作)。dcom與iiop/giop請(qǐng)求的另一個(gè)細(xì)微差別是在傳輸體中參數(shù)值的格式。在dcom中,傳輸體用網(wǎng)絡(luò)數(shù)據(jù)表達(dá)(ndr)的格式來(lái)寫(xiě),在iiop/giop中,傳輸體用公共數(shù)據(jù)表達(dá)(cdr)的格式來(lái)寫(xiě)。ndr和 cdr分別處理在各種平臺(tái)上的不同的數(shù)據(jù)表達(dá)。但是在這兩種格式之間有一些小的差別,這使它們相互之間并不兼容。
在orpc與rpc協(xié)議之間的另一個(gè)重要的不同是通信端點(diǎn)的命名方式。在orpc協(xié)議中,對(duì)于orpc端點(diǎn)的一些可傳遞的表達(dá)方式被要求在網(wǎng)絡(luò)之間傳遞對(duì)象引用。在corba/iiop,這個(gè)表達(dá)方式被稱為可交互的對(duì)象引用(ior)。iors包含用緊湊格式表達(dá)的尋址信息,使用了它任何corba產(chǎn)品都可以決定一個(gè)對(duì)象端點(diǎn)。在dcom中,這種表達(dá)方式被稱為objref,它組合了分布的引用計(jì)算和端點(diǎn)/對(duì)象標(biāo)識(shí)。corba和dcom都提供了在網(wǎng)絡(luò)上尋找對(duì)象端點(diǎn)的高級(jí)機(jī)制,但最終這些機(jī)制都映射回到了iors或objrefs。圖3是表示一個(gè) ior/objref 怎樣與在iiop/dcom請(qǐng)求消息中的尋址信息關(guān)聯(lián)起來(lái)的。
目前的技術(shù)存在的問(wèn)題?
盡管dcom和iiop都是固定的協(xié)議,業(yè)界還沒(méi)有完全轉(zhuǎn)向其中任何一個(gè)協(xié)議。沒(méi)有融合的部分原因是文化的問(wèn)題所致。而且在當(dāng)一些組織試圖標(biāo)準(zhǔn)化一個(gè)或另一個(gè)協(xié)議的時(shí)候,兩個(gè)協(xié)議的技術(shù)適用性就被提出質(zhì)疑。傳統(tǒng)上認(rèn)為dcom和corba都是合理服務(wù)器到服務(wù)器端的通信協(xié)議。但是,二者對(duì)客戶到服務(wù)器端的通信都存在明顯的弱點(diǎn),尤其是客戶機(jī)被散布在internet上的時(shí)候。
dcom 和 corba/iiop都是依賴于單個(gè)廠商的解決方案來(lái)最大優(yōu)勢(shì)地使用協(xié)議。盡管兩個(gè)協(xié)議都在各種平臺(tái)和產(chǎn)品上被實(shí)現(xiàn)了,但現(xiàn)實(shí)是選定的發(fā)布需要采用單一廠商的實(shí)現(xiàn)。在dcom的情況下,這意味著每個(gè)機(jī)器要運(yùn)行在windows nt。(盡管dcom已經(jīng)被轉(zhuǎn)移到其它平臺(tái),但它只在windows?上獲得了廣泛的延伸)。在corba情況下,這意味著每個(gè)機(jī)器要運(yùn)行同樣的orb產(chǎn)品。的確讓兩個(gè)corba產(chǎn)品用iiop相互調(diào)用是有可能的,但是許多高級(jí)的服務(wù)(如安全和事務(wù))此時(shí)通常不是可交互的。而且,任何專(zhuān)門(mén)廠商為同樣的機(jī)器的通信所作的優(yōu)化很難起作用,除非所有的應(yīng)用被建立在同一個(gè)orb產(chǎn)品上。
dcom 和corba/iiop都依賴于周密管理的環(huán)境。兩個(gè)任意的計(jì)算機(jī)使得dcom或iiop 在環(huán)境之外被成功調(diào)用(calls out of the box)的幾率是很低的。特別是在考慮安全性的時(shí)候尤其是這樣。盡管寫(xiě)一個(gè)能成功地運(yùn)用dcom或iiop的緊縮包(shrink-wrap)應(yīng)用是可能的,但這樣做要比基于socket的應(yīng)用要更多地關(guān)注細(xì)節(jié)。這對(duì)于乏味但必需的配置和安裝管理任務(wù)特別適用。
dcom 和 corba/iiop都依賴于相當(dāng)高技術(shù)的運(yùn)行環(huán)境。盡管進(jìn)程內(nèi)的com似乎特別簡(jiǎn)單,但com/dcom遠(yuǎn)程處理程序絕對(duì)不只是幾天就解決的事情。iiop 是一個(gè)比dcom更容易實(shí)現(xiàn)的協(xié)議,但兩個(gè)協(xié)議都有相當(dāng)多的深?yuàn)W的規(guī)則來(lái)處理數(shù)據(jù)排列、類(lèi)型信息和位操作。這使得一般的程序員在沒(méi)有領(lǐng)會(huì)orb產(chǎn)品或ole32.dll的情況下去構(gòu)造一個(gè)簡(jiǎn)單的corba或dcom調(diào)用也變得很困難。
也許對(duì)dcom和corba/iiop來(lái)說(shuō),最令人難以忍受的一點(diǎn)是它們不能在internet 上發(fā)揮作用。對(duì)dcom來(lái)說(shuō),一般用戶的imac 或廉價(jià)的運(yùn)行windows 95的pc 兼容機(jī)要想使用你的服務(wù)器執(zhí)行基于領(lǐng)域認(rèn)證幾乎是不可能的。更糟的是,如果防火墻或代理服務(wù)器分隔開(kāi)了客戶和服務(wù)器的機(jī)器,任何iiop或dcom包要通過(guò)的可能性是很低的,主要是由于大多數(shù)internet連接技術(shù)對(duì)http協(xié)議的偏愛(ài)所致。盡管一些廠商如microsoft, iona和visigenic都已經(jīng)建立了通道技術(shù),但這些產(chǎn)品很容易對(duì)配置錯(cuò)誤敏感而且它們是不可交互的。
在一個(gè)服務(wù)器群落中這些問(wèn)題并不能影響dcom或iiop的使用。因?yàn)樵诜?wù)器群落中主機(jī)的數(shù)量很少(一般是成百上千,而不是成千上萬(wàn)),這就抵消了dcom基于ping的生命周期管理的成本。在服務(wù)器群落中,所有主機(jī)被一個(gè)公共管理域管理的機(jī)率很大,使得統(tǒng)一的配置變得可能。相對(duì)少量的機(jī)器也能保持商業(yè)orb產(chǎn)品可控制使用的成本,因?yàn)橹恍枰倭康膐rb許可權(quán)。如果只有iiop在服務(wù)器群落中被使用,就只需要少量的orb許可權(quán)。最后,在服務(wù)器群落中所有主機(jī)有直接的ip連接也是可能的,這就消除了與防火墻相關(guān)的dcom和 iiop問(wèn)題。
http作為一個(gè)更好的rpc
在服務(wù)器群落中使用dcom 和corba 是通用的做法,但客戶機(jī)則使用http進(jìn)入服務(wù)器群落。http與rpc的協(xié)議很相似,它簡(jiǎn)單、配置廣泛,并且對(duì)防火墻比其它協(xié)議更容易發(fā)揮作用。http請(qǐng)求一般由web服務(wù)器軟件(如iis和apache)來(lái)處理,但越來(lái)越多的應(yīng)用服務(wù)器產(chǎn)品正在支持http作為除dcom和iiop外的又一個(gè)協(xié)議。
象dcom和iiop一樣,http層通過(guò)tcp/ip進(jìn)行請(qǐng)求/響應(yīng)通信。一個(gè)http的客戶端用tcp連接到http服務(wù)器。在http中使用的標(biāo)準(zhǔn)端口號(hào)是80,但任何其它端口也能被使用。在建立tcp連接后,客戶端可以發(fā)送一個(gè)請(qǐng)求消息到服務(wù)器端。服務(wù)器在處理請(qǐng)求后發(fā)回一個(gè)http響應(yīng)消息到客戶端。請(qǐng)求和響應(yīng)消息都可以包含任意的傳輸體的信息,通常用content-length和content-type的 http 頭來(lái)標(biāo)記。下面是一個(gè)合法的http請(qǐng)求消息:
post /foobar http/1.1
host: 209.110.197.12
content-type: text/plain
content-length: 12
hello, world
你可能已經(jīng)注意到http頭只是一般文本。這使得用包檢查程序或基于文本的internet工具(如telnet)來(lái)診斷http問(wèn)題變得更容易。http基于文本的屬性也使得http更容易適用于在中流行的低技術(shù)水平的編程環(huán)境。
http請(qǐng)求的第一行包含三個(gè)組件:http方法,請(qǐng)求-uri,協(xié)議版本。在前面的例子中,這些分別對(duì)應(yīng)于post, /foobar, 和 http/1.1。internet工程任務(wù)組(ietf)已經(jīng)標(biāo)準(zhǔn)化了數(shù)量固定的http方法。get是http用來(lái)訪問(wèn)web的方法。 post是建立應(yīng)用程序的最常用的http方法。和get不一樣,post允許任意數(shù)據(jù)從客戶端發(fā)送到服務(wù)器端。請(qǐng)求uri (uniform resource identifier)是一個(gè)http服務(wù)器端軟件,它用來(lái)識(shí)別請(qǐng)求的目標(biāo)的簡(jiǎn)單的標(biāo)識(shí)符(它更象一個(gè)iiop/giop object_key 或一個(gè)dcom ipid)。關(guān)于uris更多的信息請(qǐng)參照"uris, urls, and urns"。在這個(gè)例子中協(xié)議的版本是http/1.1, 它表示遵守rfc 2616的規(guī)則。http/1.1比http/1.0多增加了幾個(gè)特性,包括對(duì)大塊數(shù)據(jù)傳輸?shù)闹С忠约皩?duì)在幾個(gè)http請(qǐng)求之間保持tcp連接的支持。
請(qǐng)求的第三行和第四行指定了請(qǐng)求體的尺寸和類(lèi)型。content-length 頭指定了體信息的比特?cái)?shù)。content-type類(lèi)型標(biāo)識(shí)符指定mime類(lèi)型為體信息的語(yǔ)法。http (象 dce一樣) 允許服務(wù)器和客戶端協(xié)商用于編制信息的傳輸語(yǔ)法。大多數(shù)dce應(yīng)用采用ndr.。大多數(shù)web應(yīng)用采用text/html 或其它基于文本的語(yǔ)法。
注意在上面樣例中content-length頭與請(qǐng)求體之間的空行。不同的http頭被carriage-return/行碼序列劃定界限。這些頭與體之間用另外的carriage-return/行碼序列來(lái)劃定界限。請(qǐng)求接著包括原始字節(jié),這些字節(jié)的語(yǔ)法和長(zhǎng)度由content-length和content-type http 頭來(lái)識(shí)別。在這個(gè)例子中,內(nèi)容是十二字節(jié)的普通文本字符串"hello, world"。
在處理了請(qǐng)求之后,http服務(wù)器被期望發(fā)回一個(gè)http響應(yīng)到客戶端。響應(yīng)必須包括一個(gè)狀態(tài)代碼來(lái)表示請(qǐng)求的結(jié)果。響應(yīng)也可以包含任意的體信息。下面是一個(gè)http響應(yīng)消息:
200 ok
content-type: text/plain
content-length: 12
dlrow ,olleh
在這個(gè)例子中,服務(wù)器返回狀態(tài)代碼200,它是http中標(biāo)準(zhǔn)的成功代碼。如果服務(wù)器端不能破解請(qǐng)求代碼,它將返回下列的響應(yīng):
400 bad request
content-length: 0
如果http服務(wù)器決定到目標(biāo)uri的請(qǐng)求應(yīng)該臨時(shí)轉(zhuǎn)向另外的一個(gè)不同的uri,下列響將被返回:
307 temporarily moved
location: http://209.110.197.44/foobar
content-length: 0
這個(gè)響應(yīng)告知客戶,請(qǐng)求將能夠通過(guò)重新傳遞它到在location頭中指定的地址來(lái)被滿足。
所有的標(biāo)準(zhǔn)狀態(tài)碼和頭都在rfc 2616中被描述。它們中很少的內(nèi)容與soap用戶直接相關(guān),但有一個(gè)明顯的例外。在http/1.1,底層的tcp連接在多個(gè)請(qǐng)求/響應(yīng)對(duì)之間重用。http connection頭允許客戶端或服務(wù)器中任何一方關(guān)閉底層的連接。通過(guò)增加下列http頭到請(qǐng)求或響應(yīng)中,雙方都會(huì)要求在處理請(qǐng)求后關(guān)閉它們的tcp連接:
connection: close
當(dāng)與http/1.0軟件交互時(shí),為了保持tcp連接,建議發(fā)送方加入下列http頭到每個(gè)請(qǐng)求或響應(yīng)中:
connection: keep-alive
這個(gè)頭使缺省的http/1.0協(xié)議在每次響應(yīng)后重新開(kāi)始tcp連接的行為無(wú)法使用。
http的一個(gè)優(yōu)點(diǎn)是它正被廣泛的使用和接受。圖4表示了一個(gè)簡(jiǎn)單的java程序,它發(fā)送前面表示的請(qǐng)求并從響應(yīng)中解析出結(jié)果字符串。
下面則是一個(gè)簡(jiǎn)單的c程序用cgi來(lái)讀取來(lái)自http請(qǐng)求的字符串并通過(guò)http響應(yīng)把它的逆序串返回。
#include <stdio.h>
int main(int argc, char **argv) {
char buf[4096];
int cb = read(0, buf, sizeof(buf));
buf[cb] = 0;
strrev(buf);
printf("200 ok/r/n");p>
printf("content-type: text/plain/r/n");
printf("content-length: %d/r/n", cb);
printf("/r/n");
printf(buf);
return 0;
圖5表示了一個(gè)更流行的版本,服務(wù)器的實(shí)現(xiàn)是用java servlet,以避免cgi的每個(gè)請(qǐng)求一個(gè)進(jìn)程的開(kāi)銷(xiāo)。
一般來(lái)說(shuō)cgi是花費(fèi)代價(jià)最小的寫(xiě)http服務(wù)器端代碼的方法。實(shí)際上,每一個(gè)http服務(wù)器端產(chǎn)品都提供了一個(gè)更有效的機(jī)制來(lái)讓你的代碼處理一個(gè)http請(qǐng)求。iis提供了asp和isapi作為寫(xiě)http代碼的機(jī)制。apache允許你用運(yùn)行在apache后臺(tái)程序中的 c或perl來(lái)寫(xiě)模塊。大多數(shù)應(yīng)用服務(wù)器軟件允許你寫(xiě)java servlet,com組件,ejb session beans或基于可攜帶對(duì)象適配器(poa)接口的corba servants。
xml 作為一個(gè)更好的網(wǎng)絡(luò)數(shù)據(jù)表達(dá)方式(ndr)
http是一個(gè)相當(dāng)有用的rpc協(xié)議,它提供了iiop或dcom在組幀、連接管理以及序列化對(duì)象應(yīng)用等方面大部分功能的支持。( 而且urls與iors和objrefs在功能上令人驚嘆的接近)。http所缺少的是用單一的標(biāo)準(zhǔn)格式來(lái)表達(dá)一個(gè)rpc調(diào)用中的參數(shù)。這則正是xml的用武之地。
象ndr和cdr,xml是一個(gè)與平臺(tái)無(wú)關(guān)的中性的數(shù)據(jù)表達(dá)協(xié)議。xml允許數(shù)據(jù)被序列化成一個(gè)可以傳遞的形式,使得它容易地在任何平臺(tái)上被解碼。xml有以下不同于ndr和cdr的特點(diǎn):
有大量xml編碼和解碼軟件存在于每個(gè)編程環(huán)境和平臺(tái)上
xml基于文本,相當(dāng)容易用低技術(shù)水平的編程環(huán)境來(lái)處理
xml是特別靈活的格式,它容易用一致的方式來(lái)被擴(kuò)展
為支持可擴(kuò)展性,在xml中每一個(gè)元素和屬性有一個(gè)名域uri與它相聯(lián)系,這個(gè)uri用xmlns屬性來(lái)指定。
考慮下面的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。第二個(gè)uri也是一個(gè)url的事實(shí)是不重要的。在這兩種情況下,uri簡(jiǎn)單地被用來(lái)消除元素<reverse_string>,<string1>,<comment>和任何碰巧有同樣標(biāo)記名的其它元素間的歧義。
為了方便,xml允許名域uris被映射為局部唯一的前綴。這意味著下面的xml文檔在語(yǔ)義上等同于上面的文檔:
<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>
后面的形式對(duì)作者來(lái)說(shuō)更容易,尤其是如果有許多名域uris在使用時(shí)。
xml也支持帶類(lèi)型的數(shù)據(jù)表達(dá)。正在推出的xml schema規(guī)范為描述xml數(shù)據(jù)類(lèi)型標(biāo)準(zhǔn)化了一個(gè)詞匯集。下面是一個(gè)元素<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>
這個(gè)xml schema定義闡述了xml名域urn:schemas-develop-com:stringprocs包含了一個(gè)名為<reverse_string>的元素,這個(gè)元素包含了一個(gè)名為string1的子元素(類(lèi)型為string),它被0個(gè)或更多沒(méi)有指定的元素所遵守。
xml schema 規(guī)范還定義了一組內(nèi)置的原始數(shù)據(jù)類(lèi)型和建立一個(gè)xml文檔中元素的類(lèi)型的機(jī)制。下面的xml文檔用xml schema類(lèi)型屬性來(lái)把元素和類(lèi)型名聯(lián)系在一起:
<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描述的新的一個(gè)機(jī)制在本文寫(xiě)作的時(shí)候正在標(biāo)準(zhǔn)化過(guò)程中。
http + xml = soap
soap把xml的使用代碼化為請(qǐng)求和響應(yīng)參數(shù)編碼模式,并用http作傳輸。這似乎有點(diǎn)抽象。具體地講,一個(gè)soap方法可以簡(jiǎn)單地看作遵循soap編碼規(guī)則的http請(qǐng)求和響應(yīng)。一個(gè)soap終端則可以看作一個(gè)基于http的url,它用來(lái)識(shí)別方法調(diào)用的目標(biāo)。象corba/iiop一樣,soap不需要具體的對(duì)象被綁定到一個(gè)給定的終端,而是由具體實(shí)現(xiàn)程序來(lái)決定怎樣把對(duì)象終端標(biāo)識(shí)符映射到服務(wù)器端的對(duì)象。
soap請(qǐng)求是一個(gè)http post請(qǐng)求。soap請(qǐng)求的content-type必須用text/xml。而且它必須包含一個(gè)請(qǐng)求-uri。服務(wù)器怎樣解釋這個(gè)請(qǐng)求-uri是與實(shí)現(xiàn)相關(guān)的,但是許多實(shí)現(xiàn)中可能用它來(lái)映射到一個(gè)類(lèi)或者一個(gè)對(duì)象。一個(gè)soap請(qǐng)求也必須用soapmethodname http頭來(lái)指明將被調(diào)用的方法。簡(jiǎn)單地講,soapmethodname頭是被uri指定范圍的應(yīng)用相關(guān)的方法名,它是用#符作為分隔符將方法名與uri分割開(kāi):
soapmethodname: urn:strings-com:istring#reverse
這個(gè)頭表明方法名是reverse,范圍uri是urn:strings-com:istring。 在soap中,規(guī)定方法名范圍的名域uri在功能上等同于在dcom 或 iiop中規(guī)定方法名范圍的接口id。
簡(jiǎn)單的說(shuō),一個(gè)soap請(qǐng)求的http體是一個(gè)xml文檔,它包含方法中[in]和[in,out]參數(shù)的值。這些值被編碼成為一個(gè)顯著的調(diào)用元素的子元素,這個(gè)調(diào)用元素具有soapmethodname http頭的方法名和名域uri。調(diào)用元素必須出現(xiàn)在標(biāo)準(zhǔn)的soap <envelope>和<body>元素內(nèi)(后面會(huì)更多討論這兩個(gè)元素)。下面是一個(gè)最簡(jiǎn)單的soap方法請(qǐng)求:
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>下的第一個(gè)子元素相匹配,否則調(diào)用將被拒絕。這允許防火墻管理員在不解析xml的情況下有效地過(guò)濾對(duì)一個(gè)具體方法的調(diào)用。
soap響應(yīng)的格式類(lèi)似于請(qǐng)求格式。響應(yīng)體包含方法的[out]和 [in,out]參數(shù),這個(gè)方法被編碼為一個(gè)顯著的響應(yīng)元素的子元素。這個(gè)元素的名字與請(qǐng)求的調(diào)用元素的名字相同,但以response后綴來(lái)連接。下面是對(duì)前面的soap請(qǐng)求的soap響應(yīng):
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>
這里響應(yīng)元素被命名為reverseresponse,它是方法名緊跟response后綴。要注意的是這里是沒(méi)有soapmethodname http頭的。這個(gè)頭只在請(qǐng)求消息中需要,在響應(yīng)消息中并不需要。
圖6和圖7表明soap是怎樣與以前討論的orpc協(xié)議相互對(duì)應(yīng)的。讓許多soap新手困惑的是soap中沒(méi)有關(guān)于soap服務(wù)器怎樣使用請(qǐng)求頭來(lái)分發(fā)請(qǐng)求的要求;這被留為一個(gè)實(shí)現(xiàn)上的細(xì)節(jié)。一些soap服務(wù)器將映射請(qǐng)求-uris到類(lèi)名,并分派調(diào)用到靜態(tài)方法或到在請(qǐng)求持續(xù)期內(nèi)存活的類(lèi)的實(shí)例。其它soap服務(wù)器則將請(qǐng)求-uris映射到始終存活的對(duì)象,經(jīng)常是用查詢字符串來(lái)編碼一個(gè)用來(lái)定位在服務(wù)器進(jìn)程中的對(duì)象關(guān)鍵字。還有一些其它的soap服務(wù)器用http cookies來(lái)編碼一個(gè)對(duì)象關(guān)鍵字,這個(gè)關(guān)鍵字可被用來(lái)在每次方法請(qǐng)求中恢復(fù)對(duì)象的狀態(tài)。重要的是客戶對(duì)這些區(qū)別并不知道。客戶軟件只是簡(jiǎn)單遵循h(huán)ttp和xml的規(guī)則來(lái)形成soap請(qǐng)求,讓服務(wù)器自由以它認(rèn)為最合適的方式來(lái)為請(qǐng)求服務(wù)。
soap體的核心
soap的xml特性是為把數(shù)據(jù)類(lèi)型的實(shí)例序列化成xml的編碼模式。為了達(dá)到這個(gè)目的,soap不要求使用傳統(tǒng)的rpc風(fēng)格的代理。而是一個(gè)soap方法調(diào)用包含至少兩個(gè)數(shù)據(jù)類(lèi)型:請(qǐng)求和響應(yīng)。考慮這下面?zhèn)€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協(xié)議下,account和amount參數(shù)的值將出現(xiàn)在請(qǐng)求消息中,newbalance,overdrawn參數(shù)的值,還有amount參數(shù)的更新值將出現(xiàn)在響應(yīng)消息中。
soap把方法請(qǐng)求和方法響應(yīng)提升到了一流狀態(tài)。在soap中,請(qǐng)求和響應(yīng)實(shí)際上類(lèi)型的實(shí)例。為了理解一個(gè)方法比如ibank::withdraw怎樣映射一個(gè)soap請(qǐng)求和響應(yīng)類(lèi)型,考慮下列的數(shù)據(jù)類(lèi)型:
struct withdraw {
long account;
float amount;
};
這是一個(gè)所有的請(qǐng)求參數(shù)被打包成為一個(gè)單一的數(shù)據(jù)類(lèi)型。同樣下面的數(shù)據(jù)表示打包所有響應(yīng)參數(shù)到一個(gè)單一的數(shù)據(jù)類(lèi)型。
struct withdrawresponse {
float newbalance;
float amount;
variant_bool overdrawn;
};
再給出下面的簡(jiǎn)單的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)
你能夠想象底層的代理(可能是一個(gè)soap,dcom,或iiop代理)看上去象圖8中所表示的那樣。這里,在發(fā)送請(qǐng)求消息之前,參數(shù)被序列化成為一個(gè)請(qǐng)求對(duì)象。同樣被響應(yīng)消息接收到的響應(yīng)對(duì)象被反序列化為參數(shù)。一個(gè)類(lèi)似的轉(zhuǎn)變同樣發(fā)生在調(diào)用的服務(wù)器端。
當(dāng)通過(guò)soap調(diào)用方法時(shí),請(qǐng)求對(duì)象和響應(yīng)對(duì)象被序列化成一種已知的格式。每個(gè)soap體是一個(gè)xml文檔,它具有一個(gè)顯著的稱為<envelope>的根元素。標(biāo)記名<envelope>由soap uri (urn:schemas-xmlsoap-org:soap.v1)來(lái)劃定范圍,所有soap專(zhuān)用的元素和屬性都是由這個(gè)uri來(lái)劃定范圍的。soap envelope包含一個(gè)可選的<header>元素,緊跟一個(gè)必須的<body>元素。<body>元素也有一個(gè)顯著的根元素,它或者是一個(gè)請(qǐng)求對(duì)象或者是一個(gè)響應(yīng)對(duì)象。下面是一個(gè)ibank::withdraw請(qǐng)求的編碼:
<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>
下列響應(yīng)消息被編碼為:
<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]參數(shù)出現(xiàn)在兩個(gè)消息中。
在檢查了請(qǐng)求和響應(yīng)對(duì)象的格式后,你可能已經(jīng)注意到序列化格式通常是:
<t:typename xmlns:t='namespaceuri'> ;
<fieldname1>field1value</fieldname1>
<fieldname2>field2value</fieldname2>
</t:typename>
在請(qǐng)求的情況下,類(lèi)型是隱式的c風(fēng)格的結(jié)構(gòu),它由對(duì)應(yīng)方法中的[in]和[in, out]參數(shù)組成。對(duì)響應(yīng)來(lái)說(shuō),類(lèi)型也是隱式的c風(fēng)格的結(jié)構(gòu),它由對(duì)應(yīng)方法中的[out]和[in, out]參數(shù)組成。這種每個(gè)域?qū)?yīng)一個(gè)子元素的風(fēng)格有時(shí)被稱為元素正規(guī)格式(enf)。一般情況下,soap只用xml特性來(lái)傳達(dá)描述包含在元素內(nèi)容中信息的注釋。
象dcom和iiop一樣,soap支持協(xié)議頭擴(kuò)展。soap用可選的<header>元素來(lái)傳載被協(xié)議擴(kuò)展所使用的信息。如果客戶端的soap軟件包含要發(fā)送頭信息,原始的請(qǐng)求將可能如圖9所示。在這種情況下命名causality的頭將與請(qǐng)求一起序列化。收到請(qǐng)求后,服務(wù)器端軟件能查看頭的名域uri,并處理它識(shí)別出的頭擴(kuò)展。這個(gè)頭擴(kuò)展被http://comstuff.com uri識(shí)別,并期待一個(gè)如下的對(duì)象:
struct causality {
uuid id;
};
在這種情況下的請(qǐng)求,如果頭元素的uri不能被識(shí)別,頭元素可以被安全地忽略。
但你不能安全的忽略所有的soap體中的頭元素。如果一個(gè)特定的soap頭對(duì)正確處理消息是很關(guān)鍵的,這個(gè)頭元素能被用soap屬性mustunderstand=’true’標(biāo)記為必須的。這個(gè)屬性告訴接收者頭元素必須被識(shí)別并被處理以確保正確的使用。為了強(qiáng)迫前面causality頭成為一個(gè)必須的頭,消息將被寫(xiě)成如下形式:
<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軟件遇到不能識(shí)別必須的頭元素情況時(shí),必須拒絕這個(gè)消息并出示一個(gè)錯(cuò)誤。如果服務(wù)器在一個(gè)soap請(qǐng)求中發(fā)現(xiàn)一個(gè)不能識(shí)別的必須的頭元素,它必須返回一個(gè)錯(cuò)誤響應(yīng)并且不發(fā)送任何調(diào)用到目標(biāo)對(duì)象。如果客戶端在一個(gè)soap請(qǐng)求中發(fā)現(xiàn)一個(gè)不能識(shí)別出的必須的頭元素,它必須向調(diào)用者返回一個(gè)運(yùn)行時(shí)錯(cuò)誤。(在com情況下,這將映射為一個(gè)明顯的hresult)
soap數(shù)據(jù)類(lèi)型
在soap消息中,每個(gè)元素可能是一個(gè)soap結(jié)構(gòu)元素,一個(gè)根元素,一個(gè)存取元素或一個(gè)獨(dú)立的元素。在soap中,soap:envelope, soap:body和 soap:header 是唯一的三個(gè)結(jié)構(gòu)元素。它們的基本關(guān)系由下列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元素的四種類(lèi)型中,除了結(jié)構(gòu)元素外都被用作表達(dá)類(lèi)型的實(shí)例或?qū)σ粋€(gè)類(lèi)型實(shí)例的引用。
根元素是顯著的元素,它是soap:body 或是 soap:header的直接的子元素。其中soap: body只有一個(gè)根元素,它表達(dá)調(diào)用、響應(yīng)或錯(cuò)誤對(duì)象。這個(gè)根元素必須是soap:body的第一個(gè)子元素,它的標(biāo)記名和域名uri必須與http soapmethodname頭或在錯(cuò)誤消息情況下的soap:fault相對(duì)應(yīng)。而soap:header元素有多個(gè)根元素,與消息相聯(lián)系的每個(gè)頭擴(kuò)展對(duì)應(yīng)一個(gè)。這些根元素必須是soap:header的直接子元素,它們的標(biāo)記名和名域uri表示當(dāng)前存在擴(kuò)展數(shù)據(jù)的類(lèi)型。
存取元素被用作表達(dá)類(lèi)型的域、屬性或數(shù)據(jù)成員。一個(gè)給定類(lèi)型的域在它的soap表達(dá)將只有一個(gè)存取元素。存取元素的標(biāo)記名對(duì)應(yīng)于類(lèi)型的域名。考慮下列java 類(lèi)定義:
package com.bofsoap.ibank;
public class adjustment {
public int account ;
public float amount ;
}
在一個(gè)soap消息中被序列化的實(shí)例如下所示:
<t:adjustment
xmlns:t='urn:develop-com:java:com.bofsoap.ibank'>
<account>3514</account>
<amount>100.0</amount>
</t:adjustment>
在這個(gè)例子中,存取元素account和amount被稱著簡(jiǎn)單存取元素,因?yàn)樗鼈冊(cè)L問(wèn)對(duì)應(yīng)于在w3c xml schema規(guī)范 (見(jiàn) http://www.w3.org/tr/xmlschema-2) 的part 2中定義的原始數(shù)據(jù)類(lèi)型的值。這個(gè)規(guī)范指定了字符串,數(shù)值,日期等數(shù)據(jù)類(lèi)型的名字和表達(dá)方式以及使用一個(gè)新的模式定義中的<datatype>結(jié)構(gòu)來(lái)定義新的原始類(lèi)型的機(jī)制。
對(duì)引用簡(jiǎn)單類(lèi)型的存取元素,元素值被簡(jiǎn)單地編碼為直接在存取元素下的字符數(shù)據(jù),如上所示。對(duì)引用組合類(lèi)型的存取元素(就是那些自身用子存取元素來(lái)構(gòu)造的存取元素),有兩個(gè)技術(shù)來(lái)對(duì)存取元素進(jìn)行編碼。最簡(jiǎn)單的方法是把被結(jié)構(gòu)化的值直接嵌入在存取元素下。考慮下面的java類(lèi)定義:
package com.bofsoap.ibank;
public class transfer {
public adjustment from;
public adjustment to;
}
如果用嵌入值編碼存取元素,在soap中一個(gè)序列化的transfer對(duì)象如下所示:
<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對(duì)象的值被直接編碼在它們的存取元素下。
在考慮組合存取元素時(shí),需要說(shuō)明幾個(gè)問(wèn)題。先考慮上面的transfer類(lèi)。類(lèi)的from和to的域是對(duì)象引用,它可能為空。soap用xml schemas的null屬性來(lái)表示空值或引用。下面例子表示一個(gè)序列化的transfer對(duì)象,它的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定義來(lái)控制的。例如下列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>
在一個(gè)元素的schema聲明中如果沒(méi)有nullable屬性,就意味著在一個(gè)xml文檔中的元素是不能為空的。null存取元素的精確格式當(dāng)前還在修訂中�要了解用更多信息參考最新版本的soap規(guī)范。
與存取元素相關(guān)的另一個(gè)問(wèn)題是由于類(lèi)型關(guān)系引起的可代換性。由于前面的adjustment類(lèi)不是一個(gè)final類(lèi)型的類(lèi),transfer對(duì)象的from和to域?qū)嶋H引用繼承類(lèi)型的實(shí)例是可能的。為了支持這種類(lèi)型兼容的替換,soap使用一個(gè)名域限定的類(lèi)型屬性的xml schema約定。這種類(lèi)型屬性的值是一個(gè)對(duì)元素具體的類(lèi)型的限制的名字。考慮下面的adjustment擴(kuò)展類(lèi):
package com.bofsoap.ibank;
public class auditedadjustment extends adjustment {
public int auditlevel;
}
給出下面java語(yǔ)言:
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對(duì)象的序列化形式如下所示:
<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屬性引用一個(gè)名域限定的類(lèi)型名,它能被反序列化程序用于實(shí)例化對(duì)象的正確類(lèi)型。因?yàn)閠o存取元素引用到一個(gè)被預(yù)料的類(lèi)型的實(shí)例(而不是一個(gè)可代替的繼承類(lèi)型),xsd:type屬性是不需要的。
剛才的transfer類(lèi)設(shè)法回避了一個(gè)關(guān)鍵問(wèn)題。如果正被序列化的transfer對(duì)象用下面這種方式初始化將會(huì)發(fā)生什么情況:
transfer xfer = new transfer();
xfer.from = new adjustment();
xfer.from.account = 3514; xfer.from.amount = -100;
xfer.to = xfer.from;
基于以前的議論,在soap 中transfer對(duì)象的序列化形式如下所示:
<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>
這個(gè)表達(dá)有兩個(gè)問(wèn)題。首先最容易理解的問(wèn)題是同樣的信息被發(fā)送了兩次,這導(dǎo)致了一個(gè)比實(shí)際所需要消息的更大的消息。一個(gè)更微妙的但是更重要的問(wèn)題是由于反序列化程序不能分辨兩個(gè)帶有同樣值的adjustment對(duì)象與在兩個(gè)地方被引用的一個(gè)單一的adjustment對(duì)象的區(qū)別,兩個(gè)存取元素間的身份關(guān)系就被丟失。如果這個(gè)消息接收者已經(jīng)在結(jié)果對(duì)象上執(zhí)行了下面的測(cè)試,(xfer.to == xfer.from)將不會(huì)返回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的事實(shí)只是比較了兩個(gè)存取元素的值而不是它們身份。
為了支持必須保持身份關(guān)系的類(lèi)型的序列化,soap支持多引用存取元素。目前我們接觸到的存取元素是單引用存取元素,也就是說(shuō),元素值是嵌入在存取元素下面的,而且其它存取元素被允許引用那個(gè)值(這很類(lèi)似于在ndr中的[unique]的概念)。多引用存取元素總是被編碼為只包含已知的soap:href屬性的空元素。soap:href屬性總是包含一個(gè)代碼片段標(biāo)識(shí)符,它對(duì)應(yīng)于存取元素引用到的實(shí)例。如果to和from存取元素已經(jīng)被編碼為多引用存取元素,序列化的transfer對(duì)象如下所示:
<t:transfer
xmlns:t='urn:develop-com:java:com.bofsoap.ibank'>
<from soap:href='#id1' />
<to soap:href='#id1' />
</t:transfer>
這個(gè)編碼假設(shè)與adjustment類(lèi)兼容的一個(gè)類(lèi)型的實(shí)例已經(jīng)在envelope中的其它地方被序列化,而且這個(gè)實(shí)例已經(jīng)被用soap:id屬性標(biāo)記,如下所示:
<t:adjustment soap:id='id1'
xmlns:t='urn:develop-com:java:com.bofsoap.ibank'>
<account>3514</account>
<amount>-100.0</amount>
</t:adjustment>
對(duì)多引用存取元素,把代碼段的標(biāo)識(shí)符(例如#id1)分解到正確的實(shí)例是反序列化程序的工作。
前面的討論解釋了多引用存取元素怎樣與它的目標(biāo)實(shí)例相關(guān)聯(lián)。下面要討論的是目標(biāo)實(shí)例在哪里被序列化。這就關(guān)系到獨(dú)立元素和包的概念。
獨(dú)立元素
在soap中,一個(gè)獨(dú)立元素表示至少被一個(gè)多引用存取元素引用的類(lèi)型的實(shí)例。所有的獨(dú)立元素用soap:id屬性作標(biāo)記,而且這個(gè)屬性的值在整個(gè)soap envelope中必須是唯一的。獨(dú)立的元素被編碼就好象是它們被一個(gè)存取元素打包,這個(gè)存取元素的標(biāo)記名是實(shí)例的名域限制的類(lèi)型名。在上面的例子中,實(shí)例的名域限制的類(lèi)型名是t:adjustment。
soap限制獨(dú)立元素能被編碼的場(chǎng)所。soap定義了一個(gè)能適用于任何元素的屬性:(soap:package)。這個(gè)屬性被用于控制獨(dú)立元素能在哪里被解碼。soap序列化規(guī)則指出獨(dú)立元素必須編碼為soap:header元素或soap:body元素的直接子元素,或者是任何其它標(biāo)記為soap:package=‘true’的元素。通過(guò)把一個(gè)元素注釋為包,你能保證編碼那個(gè)實(shí)例的xml元素是完全自包含的,并且在這個(gè)包以外沒(méi)有任何引用到這個(gè)元素的多引用存取元素。
假設(shè)transfer 類(lèi)對(duì)應(yīng)于一個(gè)方法請(qǐng)求。如果transfer類(lèi)型不是一個(gè)包,被to和from存取元素引用的獨(dú)立元素將作為soap:body元素的直接子元素出現(xiàn),如圖10所示。如果transfer類(lèi)型是一個(gè)合法的soap包類(lèi)型,編碼可能象圖11所示。注意,因?yàn)閠ransfer元素是一個(gè)包,所有多引用存取器元素都引用被包含的元素。這使得把transfer元素看成一個(gè)能從它的父輩元素中分離出的獨(dú)立的xml代碼段變得更為容易。
多引用存取元素總是引用獨(dú)立元素的模型是有一個(gè)例外的。soap允許包含字符串和二進(jìn)值數(shù)據(jù)的存取元素是多引用存取元素的目標(biāo)。這意味著下面的代碼是合法的:
<t:mytype>
<field1 soap:href="#id1" />
<field2 soap:id="id1">hello, soap</field2>
</t:mytype>
盡管事實(shí)是存取元素2有一個(gè)soap:id屬性,它實(shí)際上是一個(gè)存取元素而不是獨(dú)立元素。
soap數(shù)組
數(shù)組被編碼為組合類(lèi)型的一個(gè)特殊的例子。在soap中,一個(gè)數(shù)組必須有一個(gè)秩(維數(shù))和一個(gè)容量。一個(gè)數(shù)組被編碼為一個(gè)組合類(lèi)型,其中每一個(gè)數(shù)組元素被編碼為一個(gè)子元素,這個(gè)子元素的名字是元素的名域限制的類(lèi)型名。
假設(shè)有下面的com idl類(lèi)型定義:
struct pointlist {
long celems;
[size_is(celems)] point points[];
};
這個(gè)類(lèi)型的實(shí)例將被序列化為:
<t:pointlist xmlns:t='uri for pointlist'>
<celems>3</celems>
<points xsd:type='t:point[3]' >
<point><x>3</x><y>4</y></point>
<point><x>7</x><y>5</y></point>
<point><x>1</x><y>9</y></point>
</points>
<t:pointlist>
如果points域被標(biāo)記為[ptr]屬性,這個(gè)編碼將用一個(gè)多引用存取元素,如下所示:
<t:pointlist xmlns:t='uri for pointlist'>
<celems>3</celems>
<points soap:href="#x9" />
</t:pointlist>
<t:arrayofpoint soap:id='x9' xsd:type='t:point[3]'>
<point><x>3</x><y>4</y></point>
<point><x>7</x><y>5</y></point>
<point><x>1</x><y>9</y></point>
</t:arrayofpoint>
當(dāng)把一個(gè)數(shù)組編碼為一個(gè)獨(dú)立元素時(shí),標(biāo)記名是帶前綴arrayof的類(lèi)型名。
象ndr和cdr一樣,soap支持部分轉(zhuǎn)換的數(shù)組。如果子元素的數(shù)量少于所聲明的容量,這些元素被假設(shè)正從數(shù)組的末尾丟失。這能夠通過(guò)在正包含的數(shù)組元素上使用soap:offset屬性來(lái)被忽略。
<t:arrayofpoint soap:id='x9' xsd:type='t:point[5]'
soap:offset='[1]'>
<point><x>1</x><y>9</y></point>
</t:arrayofpoint>
soap:offset屬性表示出現(xiàn)在數(shù)組中的第一個(gè)元素的索引。在上面的例子中,元素0,2到4都是不被轉(zhuǎn)換的。soap也支持稀疏數(shù)組,這是通過(guò)使用soap:position屬性來(lái)把每個(gè)元素用它的絕對(duì)索引來(lái)注釋而實(shí)現(xiàn)的:
<t:arrayofpoint soap:id='x9' xsd:type='t:point[9]'>
<point soap:position='[3]'><x>3</x><y>4</y></point>
<point soap:position='[7]'><x>4</x><y>5</y></point>
</t:arrayofpoint>
在這個(gè)例子中,元素0到2,4到6,以及8到9都不是被轉(zhuǎn)換的。
請(qǐng)注意,在soap中數(shù)組的精確語(yǔ)法在這篇文章寫(xiě)作時(shí)還在被重新審查以調(diào)整到即將推出的w3c xml schema規(guī)范中。要不斷了解soap規(guī)范的最新版本來(lái)獲得更多的細(xì)節(jié)。
錯(cuò)誤處理
一個(gè)服務(wù)器有時(shí)將不能正確地為一個(gè)方法請(qǐng)求提供服務(wù)。這可能是由于一般的http錯(cuò)誤造成的(如請(qǐng)求-uri不能被映射到本地的資源或一個(gè)http級(jí)的安全違反)。也可能是在soap翻譯軟件中的問(wèn)題,如馬歇爾打包錯(cuò)誤或一個(gè)必須的頭不能被認(rèn)出。其它可能的原因包括一個(gè)請(qǐng)求不能正確地被服務(wù),或者應(yīng)用/對(duì)象代碼決定要返回一個(gè)應(yīng)用級(jí)的錯(cuò)誤給調(diào)用者。這些情況在soap規(guī)范中都被清楚地加以處理。
如果在分發(fā)對(duì)任何soap代碼的調(diào)用之前一個(gè)錯(cuò)誤發(fā)生在http層,一個(gè)純http響應(yīng)必須被返回。標(biāo)準(zhǔn)的http狀態(tài)代碼編號(hào)將被采用,400級(jí)的代碼表示一個(gè)客戶引發(fā)的錯(cuò)誤,500級(jí)的代碼表示服務(wù)器引發(fā)的錯(cuò)誤。這通常在代碼執(zhí)行前由web服務(wù)器軟件自動(dòng)處理。
假設(shè)在http層一切正常,錯(cuò)誤發(fā)生的下一個(gè)地方是在那些翻譯和分發(fā)對(duì)應(yīng)用代碼(如com對(duì)象和corba伺服對(duì)象)的soap調(diào)用。如果錯(cuò)誤發(fā)生在這一層,服務(wù)器必須返回一個(gè)錯(cuò)誤消息來(lái)代替一個(gè)標(biāo)準(zhǔn)的響應(yīng)消息。一個(gè)錯(cuò)誤消息是下列被編碼為soap:body的根元素的類(lèi)型的實(shí)例。
<schema
targetnamespace='urn:schemas-xmlsoap-org:soap.v1'
>
<element name='fault'>
<type>
<element name='faultcode' type='string' />
<element name='faultstring' type='string' />
<element name='runcode' type='string' />
<element name='detail' />
</type>
</element>
</schema>
faultcode存取元素必須包含一個(gè)用已知的整數(shù)表示的soap錯(cuò)誤代碼或者一個(gè)專(zhuān)門(mén)應(yīng)用的名域限制的值。當(dāng)前的soap 錯(cuò)誤代碼如圖12所示。faultstring存取元素包含對(duì)發(fā)生的錯(cuò)誤的可讀性的描述。runcode 存取元素包含一個(gè)字符串,它的值必須是yes, no或 maybe,表明被請(qǐng)求的操作實(shí)際上是否在錯(cuò)誤產(chǎn)生之前被執(zhí)行。detail存取元素是可選的,用于包含一個(gè)專(zhuān)門(mén)應(yīng)用的異常對(duì)象。
下面是一個(gè)對(duì)應(yīng)于一個(gè)包含無(wú)法識(shí)別的必須的頭元素的請(qǐng)求的soap錯(cuò)誤的例子:
<soap:envelope
xmlns:soap='urn:schemas-xmlsoap-org:soap.v1'
>
<soap:body>
<soap:fault> ;
<faultcode>200</faultcode>
<faultstring>
unrecognized 'causality' header
</faultstring>
<runcode>no</runcode>
</soap:fault>
</soap:body>
</soap:envelope>
假設(shè)具體應(yīng)用的錯(cuò)誤需要被返回,你可能看到如圖13所示的代碼。在應(yīng)用定義的錯(cuò)誤的情況下,考慮應(yīng)用的異常/錯(cuò)誤對(duì)象時(shí)detail存取元素起到了soap:body 元素的作用。
奧秘
一個(gè)遺留的http問(wèn)題還需要進(jìn)一步闡明。soap支持(但不需要)http擴(kuò)展框架約定來(lái)指定必須的http頭擴(kuò)展。這些約定主要有兩個(gè)目的。首先,它們?cè)试S任意的uri被用于限定給定的http頭的范圍(象xml名域一樣)。第二,這些約定允許把必須的頭與可選的頭區(qū)分開(kāi)來(lái)(象soap:mustunderstand)。下面是一個(gè)使用http擴(kuò)展框架來(lái)把soapmethodname頭定義成為一個(gè)必須的頭擴(kuò)展:
m-post /foobar http/1.1
host: 209.110.197.2
man: "urn:schemas-xmlsoap-org:soap.v1; ns=42"
42-soapmethodname: urn:bobnsid:ifoo#doit
man頭映射soap uri到前綴為42的頭,并表示沒(méi)有認(rèn)出soap的服務(wù)器必須返回一個(gè)http錯(cuò)誤,狀態(tài)代碼為501 (沒(méi)有被實(shí)現(xiàn)) 或 510 (沒(méi)有被擴(kuò)展)。http方法必須是m-post,表明目前是必須的頭擴(kuò)展。
結(jié)論
soap是一個(gè)被類(lèi)型化的序列化格式,它恰巧用http 作為請(qǐng)求/響應(yīng)消息傳輸協(xié)議。soap被設(shè)計(jì)為與正將出現(xiàn)的xml schema規(guī)范密切配合,并支持在internet的任何地方運(yùn)行的com, corba, perl, tcl, 和 java-language, c, python, 或 php 等程序間的互操作性。
希望本文給了你一個(gè)對(duì)這個(gè)協(xié)議具體細(xì)節(jié)的更清晰的理解。我鼓勵(lì)你用soap進(jìn)行實(shí)驗(yàn),或者試著使用soap使能的系統(tǒng)之一(列在http://www.develop.com/soap/),或者自己做一些工作。我本人發(fā)現(xiàn)采用腳本語(yǔ)言(jscript),使一個(gè)基本的soap客戶與服務(wù)器建立并運(yùn)行只花費(fèi)了不到一個(gè)小時(shí)。針對(duì)你對(duì)http和xml的熟悉程度,以及你的目標(biāo)平臺(tái)的成熟度,你所花費(fèi)的時(shí)間會(huì)有所不同。
本文來(lái)源于網(wǎng)頁(yè)設(shè)計(jì)愛(ài)好者web開(kāi)發(fā)社區(qū)http://www.html.org.cn收集整理,歡迎訪問(wèn)。