Asynchronous JavaScript and XML (Ajax) 是驅動新一代 Web 站點(流行術語為 Web 2.0 站點)的關鍵技術。Ajax 允許在不干擾 Web 應用程序的顯示和行為的情況下在后臺進行數據檢索。使用 XMLHttpRequest
函數獲取數據,它是一種 API,允許客戶端 JavaScript 通過 HTTP 連接到遠程服務器。Ajax 也是許多 mashup 的驅動力,它可將來自多個地方的內容集成為單一 Web 應用程序。
不過,由于受到瀏覽器的限制,該方法不允許跨域通信。如果嘗試從不同的域請求數據,會出現安全錯誤。如果能控制數據駐留的遠程服務器并且每個請求都前往同一域,就可以避免這些安全錯誤。但是,如果僅停留在自己的服務器上,Web 應用程序還有什么用處呢?如果需要從多個第三方服務器收集數據時,又該怎么辦?
同源策略阻止從一個域上加載的腳本獲取或操作另一個域上的文檔屬性。也就是說,受到請求的 URL 的域必須與當前 Web 頁面的域相同。這意味著瀏覽器隔離來自不同源的內容,以防止它們之間的操作。這個瀏覽器策略很舊,從 Netscape Navigator 2.0 版本開始就存在。
克服該限制的一個相對簡單的方法是讓 Web 頁面向它源自的 Web 服務器請求數據,并且讓 Web 服務器像代理一樣將請求轉發給真正的第三方服務器。盡管該技術獲得了普遍使用,但它是不可伸縮的。另一種方式是使用框架要素在當前 Web 頁面中創建新區域,并且使用 GET
請求獲取任何第三方資源。不過,獲取資源后,框架中的內容會受到同源策略的限制。
克服該限制更理想方法是在 Web 頁面中插入動態腳本元素,該頁面源指向其他域中的服務 URL 并且在自身腳本中獲取數據。腳本加載時它開始執行。該方法是可行的,因為同源策略不阻止動態腳本插入,并且將腳本看作是從提供 Web 頁面的域上加載的。但如果該腳本嘗試從另一個域上加載文檔,就不會成功。幸運的是,通過添加 JavaScript Object Notation (JSON) 可以改進該技術。
JSON 是用于在瀏覽器和服務器之間交換信息的輕量級數據格式(與 XML 相比)。JOSON 依賴于 JavaScript 開發人員,因為它是 JavaScript 對象的字符串表示。例如,假設有一個含兩個屬性的 ticker 對象:symbol 和 price。這是在 JavaScript 中定義 ticker 對象的方式:
var ticker = {symbol: 'IBM', price: 91.42}; |
并且這是它的 JSON 表示方式:
{symbol: 'IBM', price: 91.42} |
從 參考資料 查找更多有關 JSON 和將其作為數據內部交換格式的信息。清單 1 定義了一個 JavaScript 函數,調用該函數時會顯示 IBM 的股價。(我們沒有詳細介紹如何將該函數添加到 Web 頁面)。
清單 1. 定義 showPrice 函數
function showPrice(data) { alert("Symbol: " + data.symbol + ", Price: " + data.price); } |
可以將 JSON 數據作為參數傳遞,以調用該函數:
showPrice({symbol: 'IBM', price: 91.42}); // alerts: Symbol: IBM, Price: 91.42 |
現在準備將這兩個步驟包含到 Web 頁面,如清單 2 所示。
清單 2. 在 Web 頁面中包含 showPrice 函數和參數
<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> |
加載頁面后,應該看如圖 1 所示的警告。
圖 1. IBM ticker

至此,本文已展示了如何將靜態 JSON 數據作為參數調用 JavaScript 函數。不過,通過在函數調用中動態包裝 JSON 數據可以用動態數據調用函數,這是一種動態 JavaScript 插入的技術。要查看其效果,將下面一行放入名為 ticker.js 的獨立 JavaScript 文件中。
showPrice({symbol: 'IBM', price: 91.42}); |
現在改變 Web 頁面中的腳本,使其和清單 3 一樣。
清單 3. 動態 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 所示的例子中,動態插入的 JavaScript 代碼位于 ticker.js 文件中,它將真正的 JSON 數據作為參數調用 showPrice()
函數。
前面已經提到,同源策略不阻止將動態腳本元素插入文檔中。也就是說,可以動態插入來自不同域的 JavaScript,并且這些域都攜帶 JSON 數據。這其實是真正的 JSONP(JSON with Padding):打包在函數調用中的 JSON 數據。注意,為了完成該操作,Web 頁面必須在插入時具有已經定義好的回調函數,也就是我們例子中的 showPrice()
。
不過,所謂的 JSONP 服務(或 Remote JSON Service)是一種帶有附加功能的 Web 服務,該功能支持在特定于用戶的函數調用中打包返回的 JSON 數據。這種方法依賴于接受回調函數名作為請求參數的遠程服務。然后該服務生成對該函數的調用,將 JSON 數據作為參數傳遞,在到達客戶端時將其插入 Web 頁面并開始執行。
從 1.2 版本開始,jQuery 擁有對 JSONP 回調的本地支持。如果指定了 JSONP 回調,就可以加載位于另一個域的 JSON 數據,回調的語法為:url?callback=?
。
jQuery 自動將 ? 替換為要調用的生成函數名。清單 4 顯示了該代碼。
清單 4. 使用 JSONP 回調
jQuery.getJSON(url+"&callback=?", function(data) { alert("Symbol: " + data.symbol + ", Price: " + data.price); }); |
為此,jQuery 將一個全局函數附加到插入腳本時需要調用的窗口對象。另外,jQuery 也能優化非跨域調用。如果向同一個域發出請求,jQuery 就將其轉化為普通 Ajax 請求。
在上一個例子中,使用了靜態文件(ticker.js)將 JavaScript 動態插入到 Web 頁面中。盡管返回了 JSONP 回復,但它不允許您在 URL 中定義回調函數名。這不是 JSONP 服務。因此,如何才能將其轉換為真正的 JSONP 服務呢?可使用的方法很多。這里我們將分別使用 PHP 和 Java 展示兩個示例。
首先,假設您的服務在所請求的 URL 中接受了一個名為 callback
的參數。(參數名不重要,但是客戶和服務器必須都同意該名稱)。另外假設向服務發送的請求是這樣的:
http://www.yourdomain.com/jsonp/ticker?symbol=IBM&callback=showPrice |
在這種情況下,symbol
是表示請求 ticker symbol 的請求參數,而 callback
是 Web 應用程序的回調函數的名稱。使用清單 5 所示的代碼可以通過 jQuery 的 JSONP 支持調用該服務。
清單 5. 調用回調服務
jQuery.getJSON("http://www.yourdomain.com/jsonp/ticker?symbol=IBM&callback=?", function(data) { alert("Symbol: " + data.symbol + ", Price: " + data.price); }); |
注意,我們使用 ?
作為回調函數名,而非真實的函數名。因為 jQuery 會用生成的函數名替換 ?
。所以您不用定義類似于 showPrice()
的函數。
清單 6 顯示了用 PHP 實現的 JSONP 服務的一段代碼。
清單 6. 用 PHP 實現的 JSONP 服務的代碼片段
$jsonData = getDataAsJson($_GET['symbol']); echo $_GET['callback'] . '(' . $jsonData . ');'; // prints: jsonp1232617941775({"symbol" : "IBM", "price" : "91.42"}); |
清單 7 顯示了具有同樣功能的 Java™ Servlet 方法。
清單 7. 用 Java servlet 實現的 JSONP 服務
@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"}); } |
那么,如果要構建 mashup 應該怎么辦,是從第三方服務器收集內容,并在單一的 Web 頁面中顯示它們嗎?答案很簡單:您必須使用第三方 JSONP 服務。這種服務并不少。
知道如何使用 JSONP 之后,可以開始使用一些現成的 JSONP Web 服務來構建應用程序和 mashup。下面為接下來的開發項目做準備。(提示:您可以復制特定的 URL 并將其粘貼到瀏覽器的地址欄,以檢查生成的 JSONP 響應)。
Digg API:來自 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:來自 Flickr 的最新貓圖片:
http://api.flickr.com/services/feeds/photos_public.gne?tags=cat&tagmode=any &format=json&jsoncallback=? |
Yahoo Local Search API:在郵編為 10504 的地區搜索比薩:
http://local.yahooapis.com/LocalSearchService/V3/localSearch?appid=YahooDemo&query=pizza &zip=10504&results=2&output=json&callback=? |
JSONP 是構建 mashup 的強大技術,但不幸的是,它并不是所有跨域通信需求的萬靈藥。它有一些缺陷,在提交開發資源之前必須認真考慮它們。第一,也是最重要的一點,沒有關于 JSONP 調用的錯誤處理。如果動態腳本插入有效,就執行調用;如果無效,就靜默失敗。失敗是沒有任何提示的。例如,不能從服務器捕捉到 404 錯誤,也不能取消或重新開始請求。不過,等待一段時間還沒有響應的話,就不用理它了。(未來的 jQuery 版本可能有終止 JSONP 請求的特性)。
JSONP 的另一個主要缺陷是被不信任的服務使用時會很危險。因為 JSONP 服務返回打包在函數調用中的 JSON 響應,而函數調用是由瀏覽器執行的,這使宿主 Web 應用程序更容易受到各類攻擊。如果打算使用 JSONP 服務,了解它能造成的威脅非常重要。(參見 參考資料 了解更多信息)。
在該系列的第一篇文章中,我們講解了如何結合使用 JSONP 和 jQuery 快速構建強大的 mashup。主要主題包括:
- 瀏覽器同源策略的限制以及解決辦法
- 作為一種有效的跨域通信技術,JSONP 能夠繞過當前瀏覽器的同源策略限制
- JSONP 使 Web 應用程序開發人員能夠快速構建 mashup
- 示例 JSONP 服務及其使用:Ticker 服務
本系列的下一篇文章將介紹 Yahoo! 查詢語言(YQL),這種單端點 JSONP 服務允許您跨 Web 查詢、過濾和合并數據。最后還使用 YQL 和 jQuery 構建 mashup 應用程序。
- 了解 JSON,它是針對 Java 和其他語言的 JavaScript Object Notation API。
- 閱讀 “掌握 Ajax,第 10 部分: 使用 JSON 進行數據傳輸”,并了解在 Ajax 應用程序中使用 JSON 作為數據交換格式。
- 通過包含三部分的系列文章 “使用 jQuery”了解 jQuery。
- Browser Security Handbook 提供關于同源策略的詳細描述。
- “征服 Ajax 應用程序的安全威脅”為確保 mashup 應用程序的安全提供技巧和最佳實踐。
- “打造安全 Ajax mashup 的未來”講解了如何為混合 Web 應用程序改進瀏覽器。
- 瀏覽 技術書店,閱讀有關這些主題和其他技術主題的圖書。