隨筆-59  評(píng)論-31  文章-0  trackbacks-0

          簡(jiǎn)介

          Asynchronous JavaScript and XML (Ajax) 是驅(qū)動(dòng)新一代 Web 站點(diǎn)(流行術(shù)語(yǔ)為 Web 2.0 站點(diǎn))的關(guān)鍵技術(shù)。Ajax 允許在不干擾 Web 應(yīng)用程序的顯示和行為的情況下在后臺(tái)進(jìn)行數(shù)據(jù)檢索。使用 XMLHttpRequest 函數(shù)獲取數(shù)據(jù),它是一種 API,允許客戶端 JavaScript 通過(guò) HTTP 連接到遠(yuǎn)程服務(wù)器。Ajax 也是許多 mashup 的驅(qū)動(dòng)力,它可將來(lái)自多個(gè)地方的內(nèi)容集成為單一 Web 應(yīng)用程序。

          不過(guò),由于受到瀏覽器的限制,該方法不允許跨域通信。如果嘗試從不同的域請(qǐng)求數(shù)據(jù),會(huì)出現(xiàn)安全錯(cuò)誤。如果能控制數(shù)據(jù)駐留的遠(yuǎn)程服務(wù)器并且每個(gè)請(qǐng)求都前往同一域,就可以避免這些安全錯(cuò)誤。但是,如果僅停留在自己的服務(wù)器上,Web 應(yīng)用程序還有什么用處呢?如果需要從多個(gè)第三方服務(wù)器收集數(shù)據(jù)時(shí),又該怎么辦?

          理解同源策略限制

          同源策略阻止從一個(gè)域上加載的腳本獲取或操作另一個(gè)域上的文檔屬性。也就是說(shuō),受到請(qǐng)求的 URL 的域必須與當(dāng)前 Web 頁(yè)面的域相同。這意味著瀏覽器隔離來(lái)自不同源的內(nèi)容,以防止它們之間的操作。這個(gè)瀏覽器策略很舊,從 Netscape Navigator 2.0 版本開(kāi)始就存在。

          克服該限制的一個(gè)相對(duì)簡(jiǎn)單的方法是讓 Web 頁(yè)面向它源自的 Web 服務(wù)器請(qǐng)求數(shù)據(jù),并且讓 Web 服務(wù)器像代理一樣將請(qǐng)求轉(zhuǎn)發(fā)給真正的第三方服務(wù)器。盡管該技術(shù)獲得了普遍使用,但它是不可伸縮的。另一種方式是使用框架要素在當(dāng)前 Web 頁(yè)面中創(chuàng)建新區(qū)域,并且使用 GET 請(qǐng)求獲取任何第三方資源。不過(guò),獲取資源后,框架中的內(nèi)容會(huì)受到同源策略的限制。

          克服該限制更理想方法是在 Web 頁(yè)面中插入動(dòng)態(tài)腳本元素,該頁(yè)面源指向其他域中的服務(wù) URL 并且在自身腳本中獲取數(shù)據(jù)。腳本加載時(shí)它開(kāi)始執(zhí)行。該方法是可行的,因?yàn)橥床呗圆蛔柚箘?dòng)態(tài)腳本插入,并且將腳本看作是從提供 Web 頁(yè)面的域上加載的。但如果該腳本嘗試從另一個(gè)域上加載文檔,就不會(huì)成功。幸運(yùn)的是,通過(guò)添加 JavaScript Object Notation (JSON) 可以改進(jìn)該技術(shù)。

          JSON 和 JSONP

          JSON 是用于在瀏覽器和服務(wù)器之間交換信息的輕量級(jí)數(shù)據(jù)格式(與 XML 相比)。JOSON 依賴(lài)于 JavaScript 開(kāi)發(fā)人員,因?yàn)樗?JavaScript 對(duì)象的字符串表示。例如,假設(shè)有一個(gè)含兩個(gè)屬性的 ticker 對(duì)象:symbol 和 price。這是在 JavaScript 中定義 ticker 對(duì)象的方式:

          var ticker = {symbol: 'IBM', price: 91.42};

          并且這是它的 JSON 表示方式:

          {symbol: 'IBM', price: 91.42}

          從 參考資料 查找更多有關(guān) JSON 和將其作為數(shù)據(jù)內(nèi)部交換格式的信息。清單 1 定義了一個(gè) JavaScript 函數(shù),調(diào)用該函數(shù)時(shí)會(huì)顯示 IBM 的股價(jià)。(我們沒(méi)有詳細(xì)介紹如何將該函數(shù)添加到 Web 頁(yè)面)。


          清單 1. 定義 showPrice 函數(shù)
          function showPrice(data) {     alert("Symbol: " + data.symbol + ", Price: " + data.price); }                 

          可以將 JSON 數(shù)據(jù)作為參數(shù)傳遞,以調(diào)用該函數(shù):

          showPrice({symbol: 'IBM', price: 91.42}); // alerts: Symbol: IBM, Price: 91.42                 

          現(xiàn)在準(zhǔn)備將這兩個(gè)步驟包含到 Web 頁(yè)面,如清單 2 所示。


          清單 2. 在 Web 頁(yè)面中包含 showPrice 函數(shù)和參數(shù)
          <script type="text/javascript"> function showPrice(data) {     alert("Symbol: " + data.symbol + ", Price: " + data.price); } </script> <script type="text/javascript">showPrice({symbol: 'IBM', price: 91.42});</script> 

          加載頁(yè)面后,應(yīng)該看如圖 1 所示的警告。


          圖 1. IBM ticker
          IBM ticker 

          至此,本文已展示了如何將靜態(tài) JSON 數(shù)據(jù)作為參數(shù)調(diào)用 JavaScript 函數(shù)。不過(guò),通過(guò)在函數(shù)調(diào)用中動(dòng)態(tài)包裝 JSON 數(shù)據(jù)可以用動(dòng)態(tài)數(shù)據(jù)調(diào)用函數(shù),這是一種動(dòng)態(tài) JavaScript 插入的技術(shù)。要查看其效果,將下面一行放入名為 ticker.js 的獨(dú)立 JavaScript 文件中。

          showPrice({symbol: 'IBM', price: 91.42});

          現(xiàn)在改變 Web 頁(yè)面中的腳本,使其和清單 3 一樣。


          清單 3. 動(dòng)態(tài) JavaScript 插入代碼
          <script type="text/javascript"> // This is our function to be called with JSON data function showPrice(data) {     alert("Symbol: " + data.symbol + ", Price: " + data.price); } var url = “ticker.js”; // URL of the external script // this shows dynamic script insertion var script = document.createElement('script'); script.setAttribute('src', url);  // load the script document.getElementsByTagName('head')[0].appendChild(script);  </script> 				

          在清單 3 所示的例子中,動(dòng)態(tài)插入的 JavaScript 代碼位于 ticker.js 文件中,它將真正的 JSON 數(shù)據(jù)作為參數(shù)調(diào)用 showPrice()函數(shù)。

          前面已經(jīng)提到,同源策略不阻止將動(dòng)態(tài)腳本元素插入文檔中。也就是說(shuō),可以動(dòng)態(tài)插入來(lái)自不同域的 JavaScript,并且這些域都攜帶 JSON 數(shù)據(jù)。這其實(shí)是真正的 JSONP(JSON with Padding):打包在函數(shù)調(diào)用中的 JSON 數(shù)據(jù)。注意,為了完成該操作,Web 頁(yè)面必須在插入時(shí)具有已經(jīng)定義好的回調(diào)函數(shù),也就是我們例子中的 showPrice()

          不過(guò),所謂的 JSONP 服務(wù)(或 Remote JSON Service)是一種帶有附加功能的 Web 服務(wù),該功能支持在特定于用戶的函數(shù)調(diào)用中打包返回的 JSON 數(shù)據(jù)。這種方法依賴(lài)于接受回調(diào)函數(shù)名作為請(qǐng)求參數(shù)的遠(yuǎn)程服務(wù)。然后該服務(wù)生成對(duì)該函數(shù)的調(diào)用,將 JSON 數(shù)據(jù)作為參數(shù)傳遞,在到達(dá)客戶端時(shí)將其插入 Web 頁(yè)面并開(kāi)始執(zhí)行。

          回頁(yè)首

          jQuery 的 JSONP 支持

          從 1.2 版本開(kāi)始,jQuery 擁有對(duì) JSONP 回調(diào)的本地支持。如果指定了 JSONP 回調(diào),就可以加載位于另一個(gè)域的 JSON 數(shù)據(jù),回調(diào)的語(yǔ)法為:url?callback=?

          jQuery 自動(dòng)將 ? 替換為要調(diào)用的生成函數(shù)名。清單 4 顯示了該代碼。


          清單 4. 使用 JSONP 回調(diào)
          jQuery.getJSON(url+"&callback=?", function(data) {     alert("Symbol: " + data.symbol + ", Price: " + data.price); }); 

          為此,jQuery 將一個(gè)全局函數(shù)附加到插入腳本時(shí)需要調(diào)用的窗口對(duì)象。另外,jQuery 也能優(yōu)化非跨域調(diào)用。如果向同一個(gè)域發(fā)出請(qǐng)求,jQuery 就將其轉(zhuǎn)化為普通 Ajax 請(qǐng)求。

          使用 JSONP 支持的示例服務(wù)

          在上一個(gè)例子中,使用了靜態(tài)文件(ticker.js)將 JavaScript 動(dòng)態(tài)插入到 Web 頁(yè)面中。盡管返回了 JSONP 回復(fù),但它不允許您在 URL 中定義回調(diào)函數(shù)名。這不是 JSONP 服務(wù)。因此,如何才能將其轉(zhuǎn)換為真正的 JSONP 服務(wù)呢?可使用的方法很多。這里我們將分別使用 PHP 和 Java 展示兩個(gè)示例。

          首先,假設(shè)您的服務(wù)在所請(qǐng)求的 URL 中接受了一個(gè)名為 callback 的參數(shù)。(參數(shù)名不重要,但是客戶和服務(wù)器必須都同意該名稱(chēng))。另外假設(shè)向服務(wù)發(fā)送的請(qǐng)求是這樣的:

          http://www.yourdomain.com/jsonp/ticker?symbol=IBM&callback=showPrice

          在這種情況下,symbol 是表示請(qǐng)求 ticker symbol 的請(qǐng)求參數(shù),而 callback 是 Web 應(yīng)用程序的回調(diào)函數(shù)的名稱(chēng)。使用清單 5 所示的代碼可以通過(guò) jQuery 的 JSONP 支持調(diào)用該服務(wù)。


          清單 5. 調(diào)用回調(diào)服務(wù)
          jQuery.getJSON("http://www.yourdomain.com/jsonp/ticker?symbol=IBM&callback=?",  function(data) {     alert("Symbol: " + data.symbol + ", Price: " + data.price); }); 

          注意,我們使用 ? 作為回調(diào)函數(shù)名,而非真實(shí)的函數(shù)名。因?yàn)?jQuery 會(huì)用生成的函數(shù)名替換 ?。所以您不用定義類(lèi)似于 showPrice()的函數(shù)。

          清單 6 顯示了用 PHP 實(shí)現(xiàn)的 JSONP 服務(wù)的一段代碼。


          清單 6. 用 PHP 實(shí)現(xiàn)的 JSONP 服務(wù)的代碼片段
          $jsonData = getDataAsJson($_GET['symbol']); echo $_GET['callback'] . '(' . $jsonData . ');'; // prints: jsonp1232617941775({"symbol" : "IBM", "price" : "91.42"}); 

          清單 7 顯示了具有同樣功能的 Java™ Servlet 方法。


          清單 7. 用 Java servlet 實(shí)現(xiàn)的 JSONP 服務(wù)
          @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp)    throws ServletException, IOException { 	String jsonData = getDataAsJson(req.getParameter("symbol")); 	String output = req.getParameter("callback") + "(" + jsonData + ");";  	resp.setContentType("text/javascript");            	PrintWriter out = resp.getWriter(); 	out.println(output); 	// prints: jsonp1232617941775({"symbol" : "IBM", "price" : "91.42"}); } 

          那么,如果要構(gòu)建 mashup 應(yīng)該怎么辦,是從第三方服務(wù)器收集內(nèi)容,并在單一的 Web 頁(yè)面中顯示它們嗎?答案很簡(jiǎn)單:您必須使用第三方 JSONP 服務(wù)。這種服務(wù)并不少。

          現(xiàn)成的 JSONP 服務(wù)

          知道如何使用 JSONP 之后,可以開(kāi)始使用一些現(xiàn)成的 JSONP Web 服務(wù)來(lái)構(gòu)建應(yīng)用程序和 mashup。下面為接下來(lái)的開(kāi)發(fā)項(xiàng)目做準(zhǔn)備。(提示:您可以復(fù)制特定的 URL 并將其粘貼到瀏覽器的地址欄,以檢查生成的 JSONP 響應(yīng))。

          Digg API:來(lái)自 Digg 的頭條新聞:

          http://services.digg.com/stories/top?appkey=http%3A%2F%2Fmashup.com&type=javascript &callback=? 

          Geonames API:郵編的位置信息:

          http://www.geonames.org/postalCodeLookupJSON?postalcode=10504&country=US&callback=?

          Flickr API:來(lái)自 Flickr 的最新貓圖片:

          http://api.flickr.com/services/feeds/photos_public.gne?tags=cat&tagmode=any &format=json&jsoncallback=?                 

          Yahoo Local Search API:在郵編為 10504 的地區(qū)搜索比薩:

          http://local.yahooapis.com/LocalSearchService/V3/localSearch?appid=YahooDemo&query=pizza &zip=10504&results=2&output=json&callback=? 

          回頁(yè)首

          重要提示

          JSONP 是構(gòu)建 mashup 的強(qiáng)大技術(shù),但不幸的是,它并不是所有跨域通信需求的萬(wàn)靈藥。它有一些缺陷,在提交開(kāi)發(fā)資源之前必須認(rèn)真考慮它們。第一,也是最重要的一點(diǎn),沒(méi)有關(guān)于 JSONP 調(diào)用的錯(cuò)誤處理。如果動(dòng)態(tài)腳本插入有效,就執(zhí)行調(diào)用;如果無(wú)效,就靜默失敗。失敗是沒(méi)有任何提示的。例如,不能從服務(wù)器捕捉到 404 錯(cuò)誤,也不能取消或重新開(kāi)始請(qǐng)求。不過(guò),等待一段時(shí)間還沒(méi)有響應(yīng)的話,就不用理它了。(未來(lái)的 jQuery 版本可能有終止 JSONP 請(qǐng)求的特性)。

          JSONP 的另一個(gè)主要缺陷是被不信任的服務(wù)使用時(shí)會(huì)很危險(xiǎn)。因?yàn)?JSONP 服務(wù)返回打包在函數(shù)調(diào)用中的 JSON 響應(yīng),而函數(shù)調(diào)用是由瀏覽器執(zhí)行的,這使宿主 Web 應(yīng)用程序更容易受到各類(lèi)攻擊。如果打算使用 JSONP 服務(wù),了解它能造成的威脅非常重要。(參見(jiàn) 參考資料 了解更多信息)。

          回頁(yè)首

          結(jié)束語(yǔ)

          在該系列的第一篇文章中,我們講解了如何結(jié)合使用 JSONP 和 jQuery 快速構(gòu)建強(qiáng)大的 mashup。主要主題包括:

          • 瀏覽器同源策略的限制以及解決辦法
          • 作為一種有效的跨域通信技術(shù),JSONP 能夠繞過(guò)當(dāng)前瀏覽器的同源策略限制
          • JSONP 使 Web 應(yīng)用程序開(kāi)發(fā)人員能夠快速構(gòu)建 mashup
          • 示例 JSONP 服務(wù)及其使用:Ticker 服務(wù)

          本系列的下一篇文章將介紹 Yahoo! 查詢語(yǔ)言(YQL),這種單端點(diǎn) JSONP 服務(wù)允許您跨 Web 查詢、過(guò)濾和合并數(shù)據(jù)。最后還使用 YQL 和 jQuery 構(gòu)建 mashup 應(yīng)用程序。


          參考資料

          作者簡(jiǎn)介

          http://www.ibm.com/developerworks/i/p-sozses.jpg

          Seda Özses 是 ibm.com 企業(yè)網(wǎng)管團(tuán)隊(duì)的成員之一。她主要關(guān)注 ibm.com 企業(yè) Web 門(mén)戶,負(fù)責(zé)管理 XSL 工作及其團(tuán)隊(duì)的需求。

          http://www.ibm.com/developerworks/i/p-sergul.jpg

          Salih Ergül 是一名獨(dú)立 IT 架構(gòu)師,擅長(zhǎng)電信業(yè)和銀行業(yè)的大規(guī)模、任務(wù)關(guān)鍵型的企業(yè)應(yīng)用集成項(xiàng)目。他編寫(xiě)了許多關(guān)于 Java 編程和銀行業(yè)最新工作的文章。

          為本文評(píng)分

          posted on 2011-12-10 19:18 RoyPayne 閱讀(256) 評(píng)論(0)  編輯  收藏 所屬分類(lèi): javascript
          主站蜘蛛池模板: 肇源县| 绵阳市| 城市| 长垣县| 建瓯市| 松江区| 航空| 北安市| 剑河县| 蒲城县| 泰顺县| 尉氏县| 赫章县| 荃湾区| 曲沃县| 军事| 乌苏市| 磐石市| 恭城| 清新县| 麻栗坡县| 武隆县| 黄浦区| 左权县| 工布江达县| 海原县| 文山县| 枞阳县| 泗洪县| 名山县| 闻喜县| 冕宁县| 泰州市| 共和县| 黄浦区| 汪清县| 中江县| 洛隆县| 昭觉县| 托克逊县| 西乌|