James Snell (jasnell@us.ibm.com), 軟體工程師,新興技術, IBM
James Snell 是 IBM 的 software group 中的 emerging Internet technologies 小組的一名設計師兼策略專家,在這個小組中,他在 Web 服務技術不斷髮展的體繫結構和實現中起到了積極的作用。他是 Programming Web Services with SOAP(O'Reilly 和 Associates 出版)一書的合著者。您可以通過 jasnell@us.ibm.com與 James 聯絡。
簡介: 本文介紹如何使用非同步 JavaScript 和 XML (Asynchronous JavaScript and XML, Ajax) 設計模式來實現基於 網頁瀏覽器的 SOAP Web 服務客戶機。
查看本系列更多內容
標記本文!
發布日期: 2006 年 1 月 16 日
層級: 中級
訪問情況 1059 次瀏覽
建議: 0 (添加評論)
平均分 (共 1 個評分 )
本文是系列文章的第 1 部分,示範了如何使用針對 Web 應用程式的 Ajax 設計模式來實現跨平台的基於 JavaScript 的 SOAP Web 服務客戶機。
Ajax 已普遍用於許多知名的 Web 應用程式服務,例如 GMail、Google Maps、Flickr 和 Odeo.com。通過使用非同步 XML 訊息傳遞,Ajax 為 Web 開發人員提供了一種擴充其 Web 應用程式價值和功能的途徑。這裡介紹的 Web Services JavaScript Library 擴充了該基礎機制,其通過引入對調用基於 SOAP 的 Web 服務的支援來增強 Ajax 設計模式。
從瀏覽器中調用 Web 服務
請訪問 Ajax 技術資源中心,這是有關 Ajax 編程模型資訊的一站式中心,包括很多文檔、教程、論壇、blog、wiki 和新聞。任何新資訊都能在這裡找到。
從 網頁瀏覽器中調用 SOAP Web 服務可能會比較麻煩,這是因為大多數流行的 網頁瀏覽器在產生和處理 XML 方面都略有不同。所有瀏覽器都一致實現且用於 XML 處理的標準 API 或功能少之又少。
瀏覽器實現人員一致支援的機制之一是 XMLHttpRequest API,它是 Ajax 設計模式的核心。developerWorks 網站最近發布的另一篇由 Philip McCarthy 撰寫的的文章詳細介紹了該 API。XMLHttpRequest 是一個用於執行非同步 HTTP 要求的 JavaScript 對象。Philip McCarthy 在其文章中描述了一個順序圖(請參見圖 1),此圖對於理解 XMLHttpRequest 對象如何支援 Ajax 設計非常有協助(請參閱參考資料,以獲得指向全文的連結)。
圖 1. Philip McCarthy 的 Ajax 順序圖
從此圖中,您可以清楚地看到 XMLHttpRequest 對象是如何工作的。一些運行在 網頁瀏覽器內的 JavaScript 建立了一個 XMLHttpRequest 執行個體和一個用於非同步回調的函數。然後,該指令碼使用 XMLHttpRequest 對象對伺服器執行 HTTP 操作。在接收到響應後,調用回呼函數。在該回呼函數內,可能處理返回的資料。如果返回的資料碰巧是 XML,則 XMLHttpRequest 對象將自動使用瀏覽器中內建的 XML 處理機制來解析該資料。
遺憾的是,使用 Ajax 方法的主要難題在於 XMLHttpRequest 對象自動解析 XML 的詳細過程。例如,假設我正在請求的資料是一個 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'
關於安全性
由於涉及許多實際安全問題,因此在預設情況下,大多數 網頁瀏覽器中的 XMLHttpRequest 對象都限制為只能與使用者正在查看的 Web 頁所在的域中承載的資源和服務進行互動。例如,如果我正在訪問一個位於 http://example.com/myapp/ 的頁面,則 XMLHttpRequest 將只允許訪問位於 example.com 域中的資源。對於阻止惡意應用程式代碼潛在地對其不應該訪問的資訊進行不適當的訪問,這種預防措施非常必要。因為這裡介紹的 Web 服務客戶機基於 XMLHttpRequest,所以這種限制同樣適用於您將會調用的 Web 服務。
如果您需要能夠訪問位於另一個域中的 Web 服務,您可以使用以下兩種合理的解決方案:
- 對 JavaScript 進行數位簽章。通過對 JavaScript 指令碼進行數位簽章,您就告訴了 網頁瀏覽器可以信任該指令碼不會執行任何惡意的活動,並且對 XMLHttpRequest 可以訪問的資料的限制也應該取消。
- 使用代理。一個簡單的解決方案是,通過位於載入的頁面所在的域中的代理資源來傳遞所有來自 XMLHttpRequest 的請求。該代理將 XMLHttpRequest 的請求轉寄到遠程位置,並將結果返回給瀏覽器。從 XMLHttpRequest 對象的角度來看,這種互動發生在現有的安全配置之內。
遺憾的是,以上代碼無法在 Internet Explorer Version 6 中運行,因為該瀏覽器不僅沒有實現 getElementsByTagNameNS 功能,而且事實上還使用了一種很糟糕的方法——將 XML 命名空間的首碼作為其元素和屬性名稱的一部分來對待。
Internet Explorer 缺少對 XML 命名空間的支援,這使得它很難處理命名空間密集的 XML 格式,例如採用獨立於瀏覽器的方式的 SOAP。即使要執行一些像提取結果中的屬性值這樣簡單的操作,您也必須編寫能夠在多個瀏覽器中實現一致預期行為的特殊代碼。幸運的是,這種特殊代碼可以封裝並重用。
為了從 網頁瀏覽器中調用 Web 服務,並可靠地處理 SOAP 訊息,您需要首先瞭解一些安全問題(請參見側欄“關於安全性”)。此外,您還需要編寫一個 JavaScript 指令碼庫(圖 2),以便將底層瀏覽器 XML 實現中的不一致情況抽象出來,從而使您能夠直接處理 Web 服務資料。
圖 2. 在使用 Web Services JavaScript Library 的 網頁瀏覽器中通過 Javascript 調用 Web 服務
圖 2 中的 Web Services JavaScript Library (ws.js) 是一組 JavaScript 對象和實用功能,它們為基於 SOAP 1.1 的 Web 服務提供了基本的支援。Ws.js 定義了下列對象:
- WS.Call:一個封裝了 XMLHttpRequest 的 Web 服務客戶機
- WS.QName:XML 限定名實現
- WS.Binder:自訂 XML 序列化器/還原序列化器的基礎
- WS.Handler:請求/響應處理常式的基礎
- 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 對象,該對象提供了調用 Web 服務的方法。WS.Call 主要負責與 XMLHttpRequest 對象進行互動,並處理 SOAP 響應。
WS.Call 對象公開了以下三個方法:
- add_handler。向處理鏈添加請求/響應處理常式。處理常式對象在調用 Web 服務的前後被調用,以支援可擴充的預調用處理和後調用處理。
- invoke。將指定的 SOAP.Envelope 對象發送給 Web 服務,然後在接收到響應後調用回呼函數。當調用使用文本 XML 編碼的文檔樣式的 Web 服務時,請使用此方法。
- invoke_rpc。建立一個封裝 RPC 樣式請求的 SOAP.Envelope,並將其發送到 Web 服務。當接收到響應時,調用回呼函數。
在通常情況下,WS.Call 對象只不過是位於 XMLHttpRequest 對象頂層的瘦封裝器 (thin wrapper),該封裝器能夠執行許多簡化處理的操作。這些操作包括設定 SOAP 1.1 規範要求的 SOAPAction HTTP Header。
回頁首
使用 ws.js
Web services JavaScript Library 提供的 API 非常簡單。
SOAP.* 對象(SOAP.Element
、SOAP.Envelope
、SOAP.Header
和 SOAP.Body
)提供了構建和讀取 SOAP 信封的方法,如清單 3 所示,因而處理 XML 文件物件模型的底層細節就順利地抽象出來。
清單 3. 構建一個 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. 構建一個 SOAP 信封
<Envelope xmlns="http://schemas.xmlsoap.org"> <Body> <method xmlns="urn:foo"> <param>bar</param> </method> </Body></Envelope>
如果您正在建立的 SOAP 信封代表一個 RPC 樣式的請求,則 SOAP.Body 元素提供了一個簡便方法 set_rpc
(如清單 5 所示),該方法能夠構造一個完整的 RPC 請求——包含一個指定的操作名稱、一個指定的輸入參數數組和一個 SOAP 編碼樣式的 URI。
清單 5. 構建一個 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);
每個參數都作為一個 JavaScript 對象結構進行傳遞,且可能帶有以下屬性:
- name。一個指定參數名稱的字串或 WS.QName 對象。必需。
- value。參數的值。如果該值不是一個單一資料型別(例如,字串、整數或其他),則應該指定一個能將該值序列化為適當的 XML 結構的 WS.Binder。必需。
- xsitype:標識參數的 XML 模式執行個體類型的 WS.QName(例如,
xsi:type="int"
對應 xsitype:new WS.QName('int','http://www.w3.org/2000/10/XMLSchema')
)。可選。
- encodingstyle:標識參數所使用的 SOAP 編碼樣式的 URI。可選。
- binder:能夠將參數序列化為 XML 的 WS.Binder 實現。可選。
例如,如果要指定的參數名為“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')})
。
一旦我為服務要求構建了 SOAP.Envelope,我就會將該 SOAP.Envelope 傳遞到 WS.Call 對象的 invoke
方法,以便調用該信封內編碼的方法: (new WS.Call(service_uri)).invoke(envelope, callback)
另一種可選方案是手動構建 SOAP.Envelope。我會將參數 WS.QName、參數數組和編碼樣式傳遞到 WS.Call 對象的 invoke_rpc
方法,如清單 6 所示。
清單 6. 使用 WS.Call 對象調用 Web 服務
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] } );
在調用 invoke
方法或 invoke_rpc
方法時,WS.Call 對象會建立一個基本的 XMLHttpRequest 對象,用包含 SOAP 信封的 XML 元素進行傳遞,並接收和解析響應,然後調用提供的回呼函數。
為了能夠擴充 SOAP 訊息的預先處理和後處理,WS.Call 對象允許您註冊一組 WS.Handler 對象,如清單 7 所示。對於調用周期內的每個請求、每個響應和每個錯誤,都將調用這些對象。可以通過擴充 WS.Handler JavaScript 對象來實現新的處理常式。
清單 7. 建立和註冊響應/響應處理常式
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());
處理常式對插入或提取正在傳遞的 SOAP 信封中的資訊最有用。例如,您可以設想一個處理常式自動向 SOAP Envelope 的 Header 插入適當的 Web 服務定址 (Web Services Addressing) 元素,如清單 8 中的樣本所示。
清單 8. 一個將 Web 服務定址操作 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)執行 SOAP.Element 對象的自訂序列化和還原序列化。WS.Binder 的實現必須提供以下兩個方法:
- to_soap_element。將 JavaScript 對象序列化為 SOAP.Element。傳入的第一個參數是要序列化的值。第二個參數是 SOAP.Element,必須將要序列化的值序列化為 SOAP.Element。該方法不返回任何值。
- to_value_object。將 SOAP.Element 還原序列化為 JavaScript 對象。該方法必須返回還原序列化的值對象。
清單 9. WS.Binding 實現樣本
var MyBinding = Class.create();MyBinding.prototype = (new WS.Binding()).extend({ to_soap_element : function(value,element) { ... }, to_value_object : function(element) { ... }});
回頁首
一個簡單樣本
我已經提供了一個樣本項目來闡釋 Web Services JavaScript Library 的準系統。該示範所使用的 Web 服務(如清單 10 所示)已經在 WebSphere Application Server 中進行了實現,並提供了簡單的 Hello World 功能。
清單 10. 一個簡單的基於 Java 的“Hello World”Web 服務
package example;public class HelloWorld { public String sayHello(String name) { return "Hello " + name; }}
在實現了該服務並將其部署到 WebSphere Application Server 後,該服務(清單 11)的 WSDL 描述定義了您需要傳遞的 SOAP 訊息(用於調用 Hello World 服務)。
清單 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,您可以實現一個調用 Hello World 服務的方法,如清單 12所示。
清單 12. 使用 WS.Call 調用 HelloWorld 服務
<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 應用程式中的任意位置通過調用 sayHello
函數來調用 Hello World 服務。請參見清單 13。
清單 13. 調用 sayHello 函數
<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>
調用成功後的結果 3 所示。在 Mozilla、Firefox 和 Internet Explorer 中運行該樣本應該會得到相同的結果。
圖 3. Firefox 中的 Hello World 樣本
回頁首
後續部分
使用 Web Services JavaScript Library,可以採用簡單的獨立於瀏覽器的方式將基本的 SOAP Web 服務合并到 Web 應用程式中。在本系列的下一個部分中,您不僅可以探討如何使用該庫來調用更多基於 Web 服務資源架構 (WS-Resource Framework ) 系列規範的進階 Web 服務,而且還可以瞭解擴充該 Web 服務功能並將其整合到 Web 應用程式中的方法。
回頁首
下載
描述
名字
大小
下載方法
Sample project
ws-wsajaxcode.zip
19 KB
HTTP
關於下載方法的資訊