級別: 中級
James Snell, 軟件工程師, IBM
2006 年 1 月 16 日
本文介紹如何使用異步 JavaScript 和 XML (Asynchronous JavaScript and XML, AJAX) 設(shè)計模式來實現(xiàn)基于 Web 瀏覽器的 SOAP Web 服務(wù)客戶機。
本文是一篇短的系列文章的第 1 部分,演示了如何使用針對 Web 應(yīng)用程序的 AJAX 設(shè)計模式來實現(xiàn)跨平臺的基于 JavaScript 的 SOAP Web 服務(wù)客戶機。
AJAX
已普遍用于許多知名的 Web 應(yīng)用程序服務(wù),例如 GMail、Google Maps、Flickr 和 Odeo.com。通過使用異步 XML
消息傳遞,AJAX 為 Web 開發(fā)人員提供了一種擴展其 Web 應(yīng)用程序價值和功能的途徑。這里介紹的 Web Services
JavaScript
Library 擴展了該基礎(chǔ)機制,其通過引入對調(diào)用基于 SOAP 的 Web 服務(wù)的支持來增強 AJAX 設(shè)計模式。
從瀏覽器中調(diào)用 Web 服務(wù)
從 Web 瀏覽器中調(diào)用 SOAP Web 服務(wù)可能會比較麻煩,這是因為大多數(shù)流行的 Web 瀏覽器在生成和處理 XML 方面都略有不同。所有瀏覽器都一致實現(xiàn)且用于 XML 處理的標(biāo)準(zhǔn) API 或功能少之又少。
瀏
覽器實現(xiàn)人員一致支持的機制之一是 XMLHttpRequest API,它是 AJAX 設(shè)計模式的核心。developerWorks
網(wǎng)站最近發(fā)布的另一篇由 Philip McCarthy 撰寫的的文章詳細(xì)介紹了該 API。XMLHttpRequest 是一個用于執(zhí)行異步
HTTP 請求的 JavaScript 對象。Philip McCarthy 在其文章中描述了一個順序圖(請參見圖 1),此圖對于理解 XMLHttpRequest 對象如何支持 AJAX 設(shè)計非常有幫助(請參閱參考資料,以獲得指向全文的鏈接)。
圖 1. Philip McCarthy 的 AJAX 順序圖
從
此圖中,您可以清楚地看到 XMLHttpRequest 對象是如何工作的。一些運行在 Web 瀏覽器內(nèi)的 JavaScript 創(chuàng)建了一個
XMLHttpRequest 實例和一個用于異步回調(diào)的函數(shù)。然后,該腳本使用 XMLHttpRequest 對象對服務(wù)器執(zhí)行 HTTP
操作。在接收到響應(yīng)后,調(diào)用回調(diào)函數(shù)。在該回調(diào)函數(shù)內(nèi),可能處理返回的數(shù)據(jù)。如果返回的數(shù)據(jù)碰巧是 XML,則 XMLHttpRequest
對象將自動使用瀏覽器中內(nèi)置的 XML 處理機制來解析該數(shù)據(jù)。
遺憾的是,使用 AJAX 方法的主要難題在于 XMLHttpRequest 對象自動解析 XML 的詳細(xì)過程。例如,假設(shè)我正在請求的數(shù)據(jù)是一個 SOAP 信封,其包含來自許多不同 XML 命名空間的元素,并且我希望提取 yetAnotherElement
中屬性 attr
的值。(請參見清單 1)
清單 1. 一個包含多個命名空間的 SOAP 信封
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <s:Header/> <s:Body> <m:someElement xmlns:m="http://example"> <n:someOtherElement xmlns:n="http://example" xmlns:m="urn:example"> <m:yetAnotherElement n:attr="abc" xmlns:n="urn:foo"/> </n:someOtherElement> </m:someElement> </s:Body> </s:Envelope>
|
在 Mozilla 瀏覽器和 Firefox 瀏覽器中,提取 attr
屬性值非常簡單,如清單 2所示。
清單 2. 在 Mozilla 和 Firefox 中檢索 attr 屬性值的方法不能運用在 Internet Explorer 中
var m = el.getElementsByTagNameNS( 'urn:example', 'yetAnotherElement')[0]. getAttributeNS( 'urn:foo', 'attr'); alert(m); // displays 'abc'
|
|
關(guān)于安全性
由
于涉及許多實際安全問題,因此在缺省情況下,大多數(shù) Web 瀏覽器中的 XMLHttpRequest 對象都限制為只能與用戶正在查看的 Web
頁所在的域中承載的資源和服務(wù)進(jìn)行交互。例如,如果我正在訪問一個位于 http://example.com/myapp/ 的頁面,則
XMLHttpRequest 將只允許訪問位于 example.com
域中的資源。對于阻止惡意應(yīng)用程序代碼潛在地對其不應(yīng)該訪問的信息進(jìn)行不適當(dāng)?shù)脑L問,這種預(yù)防措施非常必要。因為這里介紹的 Web 服務(wù)客戶機基于
XMLHttpRequest,所以這種限制同樣適用于您將會調(diào)用的 Web 服務(wù)。
如果您需要能夠訪問位于另一個域中的 Web 服務(wù),您可以使用以下兩種合理的解決方案:
-
對 JavaScript 進(jìn)行數(shù)字簽名。通過對 JavaScript 腳本進(jìn)行數(shù)字簽名,您就告訴了 Web 瀏覽器可以信任該腳本不會執(zhí)行任何惡意的活動,并且對 XMLHttpRequest 可以訪問的數(shù)據(jù)的限制也應(yīng)該取消。
-
使用代理。一
個簡單的解決方案是,通過位于加載的頁面所在的域中的代理資源來傳遞所有來自 XMLHttpRequest 的請求。該代理將
XMLHttpRequest 的請求轉(zhuǎn)發(fā)到遠(yuǎn)程位置,并將結(jié)果返回給瀏覽器。從 XMLHttpRequest
對象的角度來看,這種交互發(fā)生在現(xiàn)有的安全配置之內(nèi)。
|
|
遺
憾的是,以上代碼無法在 Internet Explorer Version 6 中運行,因為該瀏覽器不僅沒有實現(xiàn)
getElementsByTagNameNS 功能,而且事實上還使用了一種很糟糕的方法——將 XML
命名空間的前綴作為其元素和屬性名稱的一部分來對待。
Internet Explorer 缺少對 XML
命名空間的支持,這使得它很難處理命名空間密集的 XML 格式,例如采用獨立于瀏覽器的方式的
SOAP。即使要執(zhí)行一些像提取結(jié)果中的屬性值這樣簡單的操作,您也必須編寫能夠在多個瀏覽器中實現(xiàn)一致預(yù)期行為的特殊代碼。幸運的是,這種特殊代碼可以
封裝并重用。
為了從 Web 瀏覽器中調(diào)用 Web 服務(wù),并可靠地處理 SOAP 消息,您需要首先了解一些安全問題(請參見側(cè)欄“關(guān)于安全性”)。此外,您還需要編寫一個 JavaScript 腳本庫(圖 2),以便將底層瀏覽器 XML 實現(xiàn)中的不一致情況抽象出來,從而使您能夠直接處理 Web 服務(wù)數(shù)據(jù)。
圖 2. 在使用 Web Services JavaScript Library 的 Web 瀏覽器中通過 Javascript 調(diào)用 Web 服務(wù)
圖 2 中的 Web Services JavaScript Library (ws.js) 是一組 JavaScript 對象和實用功能,它們?yōu)榛?SOAP 1.1 的 Web 服務(wù)提供了基本的支持。Ws.js 定義了下列對象:
-
WS.Call:一個包裝了 XMLHttpRequest 的 Web 服務(wù)客戶機
-
WS.QName:XML 限定名實現(xiàn)
-
WS.Binder:自定義 XML 序列化器/反序列化器的基礎(chǔ)
-
WS.Handler:請求/響應(yīng)處理程序的基礎(chǔ)
-
SOAP.Element:包裝了 XML DOM 的基本 SOAP 元素
-
SOAP.Envelope:SOAP Envelope 對象擴展了 SOAP.Element
-
SOAP.Header:SOAP Header 對象擴展了 SOAP.Element
-
SOAP.Body:SOAP Body 對象擴展了 SOAP.Element
-
XML:用于處理 XML 的跨平臺實用方法
ws.js 的核心是 WS.Call 對象,該對象提供了調(diào)用 Web 服務(wù)的方法。WS.Call 主要負(fù)責(zé)與 XMLHttpRequest 對象進(jìn)行交互,并處理 SOAP 響應(yīng)。
WS.Call 對象公開了以下三個方法:
-
add_handler。向處理鏈添加請求/響應(yīng)處理程序。處理程序?qū)ο笤谡{(diào)用 Web 服務(wù)的前后被調(diào)用,以支持可擴展的預(yù)調(diào)用處理和后調(diào)用處理。
-
invoke。將指定的 SOAP.Envelope 對象發(fā)送給 Web 服務(wù),然后在接收到響應(yīng)后調(diào)用回調(diào)函數(shù)。當(dāng)調(diào)用使用文本 XML 編碼的文檔樣式的 Web 服務(wù)時,請使用此方法。
-
invoke_rpc。創(chuàng)建一個封裝 RPC 樣式請求的 SOAP.Envelope,并將其發(fā)送到 Web 服務(wù)。當(dāng)接收到響應(yīng)時,調(diào)用回調(diào)函數(shù)。
在
通常情況下,WS.Call 對象只不過是位于 XMLHttpRequest 對象頂層的瘦包裝器 (thin
wrapper),該包裝器能夠執(zhí)行許多簡化處理的操作。這些操作包括設(shè)置 SOAP 1.1 規(guī)范要求的 SOAPAction HTTP
Header。
使用 ws.js
Web services JavaScript Library 提供的 API 非常簡單。
SOAP.* 對象(SOAP.Element
、SOAP.Envelope
、SOAP.Header
和 SOAP.Body
)提供了構(gòu)建和讀取 SOAP 信封的方法,如清單 3 所示,因而處理 XML 文檔對象模型的底層細(xì)節(jié)就順利地抽象出來。
清單 3. 構(gòu)建一個 SOAP 信封
var envelope = new SOAP.Envelope(); var body = envelope.create_body(); var el = body.create_child(new WS.QName('method','urn:foo')); el.create_child(new WS.QName('param','urn:foo')).set_value('bar');
|
清單 4 顯示了由 清單 3 中的代碼生成的 SOAP 信封。
清單 4. 構(gòu)建一個 SOAP 信封
<Envelope xmlns="http://schemas.xmlsoap.org"> <Body> <method xmlns="urn:foo"> <param>bar</param> </method> </Body> </Envelope>
|
如果您正在創(chuàng)建的 SOAP 信封代表一個 RPC 樣式的請求,則 SOAP.Body 元素提供了一個簡便方法 set_rpc
(如清單 5 所示),該方法能夠構(gòu)造一個完整的 RPC 請求——包含一個指定的操作名稱、一個指定的輸入?yún)?shù)數(shù)組和一個 SOAP 編碼樣式的 URI。
清單 5. 構(gòu)建一個 RPC 請求信封
var envelope = new SOAP.Envelope(); var body = envelope.create_body(); body.set_rpc( new WS.QName('param','urn:foo'), new Array( {name:'param',value:'bar'} ), SOAP.NOENCODING );
|
每個參數(shù)都作為一個 JavaScript 對象結(jié)構(gòu)進(jìn)行傳遞,且可能帶有以下屬性:
-
name。一個指定參數(shù)名稱的字符串或 WS.QName 對象。必需。
-
value。參數(shù)的值。如果該值不是一個簡單數(shù)據(jù)類型(例如,字符串、整數(shù)或其他),則應(yīng)該指定一個能將該值序列化為適當(dāng)?shù)?XML 結(jié)構(gòu)的 WS.Binder。必需。
-
xsitype:標(biāo)識參數(shù)的 XML 模式實例類型的 WS.QName(例如,
xsi:type="int"
對應(yīng) xsitype:new WS.QName('int','http://www.w3.org/2000/10/XMLSchema')
)。可選。
-
encodingstyle:標(biāo)識參數(shù)所使用的 SOAP 編碼樣式的 URI。可選。
-
binder:能夠?qū)?shù)序列化為 XML 的 WS.Binder 實現(xiàn)。可選。
例如,如果要指定的參數(shù)名為“abc”、XML 命名空間為“urn:foo”、xsi:type 為“int”且值為“3”,則我會使用以下代碼:new Array({name:new WS.QName('abc','urn:foo'), value:3,
xsitype:new WS.QName('int','http://www.w3.org/2000/10/XMLSchema')})
。
一旦我為服務(wù)請求構(gòu)建了 SOAP.Envelope,我就會將該 SOAP.Envelope 傳遞到 WS.Call 對象的 invoke
方法,以便調(diào)用該信封內(nèi)編碼的方法:
(new WS.Call(service_uri)).invoke(envelope, callback)
另一種可選方案是手動構(gòu)建 SOAP.Envelope。我會將參數(shù) WS.QName、參數(shù)數(shù)組和編碼樣式傳遞到
WS.Call 對象的 invoke_rpc
方法,如清單 6 所示。
清單 6. 使用 WS.Call 對象調(diào)用 Web 服務(wù)
var call = new WS.Call(serviceURI); var nsuri = 'urn:foo'; var qn_op = new WS.QName('method',nsuri); var qn_op_resp = new WS.QName('methodResponse',nsuri); call.invoke_rpc( qn_op, new Array( {name:'param',value:'bar'} ),SOAP.NOENCODING, function(call,envelope) { // envelope is the response SOAP.Envelope // the XML Text of the response is in arguments[2] } );
|
在調(diào)用 invoke
方法或
invoke_rpc
方法時,WS.Call 對象會創(chuàng)建一個基本的 XMLHttpRequest 對象,用包含 SOAP 信封的 XML 元素進(jìn)行傳遞,并接收和解析響應(yīng),然后調(diào)用提供的回調(diào)函數(shù)。
為了能夠擴展 SOAP 消息的預(yù)處理和后處理,WS.Call 對象允許您注冊一組 WS.Handler 對象,如清單 7 所示。對于調(diào)用周期內(nèi)的每個請求、每個響應(yīng)和每個錯誤,都將調(diào)用這些對象。可以通過擴展 WS.Handler JavaScript 對象來實現(xiàn)新的處理程序。
清單 7. 創(chuàng)建和注冊響應(yīng)/響應(yīng)處理程序
var MyHandler = Class.create(); MyHandler.prototype = (new WS.Handler()).extend({ on_request : function(envelope) { // pre-request processing }, on_response : function(call,envelope) { // post-response, pre-callback processing }, on_error : function(call,envelope) { } });
var call = new WS.Call(...); call.add_handler(new MyHandler());
|
處理程序?qū)Σ迦牖蛱崛≌趥鬟f的 SOAP 信封中的信息最有用。例如,您可以設(shè)想一個處理程序自動向 SOAP Envelope 的 Header 插入適當(dāng)?shù)?Web 服務(wù)尋址 (Web Services Addressing) 元素,如清單 8 中的示例所示。
清單 8. 一個將 Web 服務(wù)尋址操作 Header 添加到請求中的處理程序示例
var WSAddressingHandler = Class.create(); WSAddressingHandler.prototype = (new WS.Handler()).extend({ on_request : function(call,envelope) { envelope.create_header().create_child( new WS.QName('Action','http://ws-addressing','wsa') ).set_value('http://www.example.com'); } });
|
WS.Binder 對象(清單 9)執(zhí)行 SOAP.Element 對象的自定義序列化和反序列化。WS.Binder 的實現(xiàn)必須提供以下兩個方法:
-
to_soap_element。將 JavaScript 對象序列化為 SOAP.Element。傳入的第一個參數(shù)是要序列化的值。第二個參數(shù)是 SOAP.Element,必須將要序列化的值序列化為 SOAP.Element。該方法不返回任何值。
-
to_value_object。將 SOAP.Element 反序列化為 JavaScript 對象。該方法必須返回反序列化的值對象。
清單 9. WS.Binding 實現(xiàn)示例
var MyBinding = Class.create(); MyBinding.prototype = (new WS.Binding()).extend({ to_soap_element : function(value,element) { ... }, to_value_object : function(element) { ... } });
|
一個簡單示例
我已經(jīng)提供了一個示例項目來闡釋 Web Services JavaScript Library 的基本功能。該演示所使用的 Web 服務(wù)(如清單 10 所示)已經(jīng)在 WebSphere Application Server 中進(jìn)行了實現(xiàn),并提供了簡單的 Hello World 功能。
清單 10. 一個簡單的基于 Java 的“Hello World”Web 服務(wù)
package example;
public class HelloWorld { public String sayHello(String name) { return "Hello " + name; } }
|
在實現(xiàn)了該服務(wù)并將其部署到 WebSphere Application Server 后,該服務(wù)(清單 11)的 WSDL 描述定義了您需要傳遞的 SOAP 消息(用于調(diào)用 Hello World 服務(wù))。
清單 11. HelloWorld.wsdl 的代碼片段
<wsdl:portType name="HelloWorld"> <wsdl:operation name="sayHello"> <wsdl:input message="impl:sayHelloRequest" name="sayHelloRequest"/> <wsdl:output message="impl:sayHelloResponse" name="sayHelloResponse"/> </wsdl:operation> </wsdl:portType>
|
通過使用 Web Services JavaScript Library,您可以實現(xiàn)一個調(diào)用 Hello World 服務(wù)的方法,如清單 12所示。
清單 12. 使用 WS.Call 調(diào)用 HelloWorld 服務(wù)
<html> <head> ... <script type="text/javascript" src="scripts/prototype.js"></script> <script type="text/javascript" src="scripts/ws.js"></script> <script type="text/javascript"> function sayHello(name, container) { var call = new WS.Call('/AjaxWS/services/HelloWorld'); var nsuri = 'http://example'; var qn_op = new WS.QName('sayHello',nsuri); var qn_op_resp = new WS.QName('sayHelloResponse',nsuri); call.invoke_rpc( qn_op, new Array( {name:'name',value:name} ),null, function(call,envelope) { var ret = envelope.get_body().get_all_children()[0]. get_all_children()[0].get_value(); container.innerHTML = ret; $('soap').innerHTML = arguments[2].escapeHTML(); } ); } </script> </head> ...
|
然后,您可以在我們的 Web 應(yīng)用程序中的任意位置通過調(diào)用 sayHello
函數(shù)來調(diào)用 Hello World 服務(wù)。請參見清單 13。
清單 13. 調(diào)用 sayHello 函數(shù)
<body> <input name="name" id="name" /> <input value="Invoke the Web Service" type="button" onclick="sayHello($('name').value,$('result'))" /> <div id="container">Result: <div id="result"> </div> <div id="soap"> </div> </div> </body>
|
調(diào)用成功后的結(jié)果如圖 3 所示。在 Mozilla、Firefox 和 Internet Explorer 中運行該示例應(yīng)該會得到相同的結(jié)果。
圖 3. Firefox 中的 Hello World 示例
后續(xù)部分
使
用 Web Services JavaScript Library,可以采用簡單的獨立于瀏覽器的方式將基本的 SOAP Web 服務(wù)合并到
Web 應(yīng)用程序中。在本系列的下一個部分中,您不僅可以探討如何使用該庫來調(diào)用更多基于 Web 服務(wù)資源框架 (WS-Resource
Framework ) 系列規(guī)范的高級 Web 服務(wù),而且還可以了解擴展該 Web 服務(wù)功能并將其集成到 Web 應(yīng)用程序中的方法。
下載
描述 |
名字 |
大小 |
下載方法 |
Sample project |
ws-wsajaxcode.zip |
19 KB |
?FTP |
參考資料
學(xué)習(xí)
獲得產(chǎn)品和技術(shù)
討論
關(guān)于作者