使用 JSONP 實現跨域通信


 

簡介

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 和 JSONP

JSON 是用於在瀏覽器和服務器之間交換信息的輕量級數據格式(與 XML 相比)。JOSON 依賴於 JavaScript 開發人員,因為它是 JavaScript 對象的字符串表示。例如,假設有一個含兩個屬性的 ticker 對象:symbol 和 price。這是在 JavaScript 中定義 ticker 對象的方式:

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

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

{symbol: 'IBM', price: 91.42}

 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. 在 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>

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

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

 現在改變 Web 頁面中的腳本

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 頁面並開始執行。


jQuery 的 JSONP 支持

從 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 請求。

使用 JSONP 支持的示例服務

在上一個例子中,使用了靜態文件(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 之后,可以開始使用一些現成的 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 服務,了解它能造成的威脅非常重要。



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM