以下我們以一個簡單的例子來說明, 這裡我們要存取的是台北市資料公開平台中的停水資訊, 你可以透過以下的 URL 取得 json 格式的停水資訊:
http://taipeicityopendata.cloudapp.net/v1/TaipeiOGDI/StopWaterInfo?format=json
如果我們使用以下的 HTML 網頁 (此程式修改自此網頁) 去存取停水資訊, 會發現瀏覽器根本不會送出請求:
<!DOCTYPE html> <html lang="en"> <head> <title>JQuery JSONP</title> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script> <script> $(document).ready(function(){ $("#useJSONP").click(function(){ $.ajax({ url: 'http://taipeicityopendata.cloudapp.net/v1/TaipeiOGDI/StopWaterInfo', data: {format: 'json'}, dataType: 'json', success: jsonpCallback }); }); }); function jsonpCallback(data){ $('#jsonpResult').text(data.d[0].stitle); } </script> </head> <body> <input type="button" id="useJSONP" value="Use JSONP"></input> <span id="jsonpResult"></span> </body> </html>
如果使用 Chrome 的開發人員工具,在 Console 頁次中會看到錯誤訊息 "XMLHttpRequest cannot load .... Origin null is not allowed by Access-Control-Allow-Origin.", 表示因為只限來源網域才能進行 ajax, 所以無法載入所要的資料。
為了解決這個問題, 就有人想出了 jsonp 的方法。由於瀏覽器在處理 <script src="...">
的時候, 並沒有來源網站的限制, 因此只要能動態的產生一個 <script>
元素, 指定 src 屬性, 就可以動態載入並執行一段 JavaScript 碼, 若是 server 端配合, 產生類似以下的程式碼:
jsonCallnack(原本的 json 資料)
那麼當瀏覽器為動態產生的 <script> 元素載入此段程式碼執行, 就會呼叫 jsonCallback 函數處理 json 資料, 達到跨網域存取資料的目的。
為了完成上述動作, 一般就是在 <script> 的 src 屬性值指定的 URL 添加 "callback=jsonCallback" 參數, server 端收到這個請求時, 就會檢查 callback 參數的值, 將此參數搭配 json 資料產生一段如同前述內容的 JavaScript 碼。
你可以發現 jsonp 要能成功, 必須仰賴 server 端配合, 而我們所存取的台北市資料公開平台就支援 jsonp, 會在發現查詢字串中有 callback 參數時, 改用 jsonp 的方式運作, 傳回一段 JavaScript 碼, 若是沒有 callback 參數, 就直接傳回 json 格式的資料。
在 jQuery 中, 只要在呼叫 ajax() 時把 dataType 選項指定為 "jsonp", ajax() 就會改用 jsonp 的方式, 動態產生 <script>, 並且自動依據 success 選項指定的 callback 函數, 在 url 選項的內容加入 "callback=XXX" 的參數, 其中 XXX 就是 success 選項所指定函數的名稱。
如果我們把之前的範例改為這樣:
$("#useJSONP").click(function(){ $.ajax({ url: 'http://taipeicityopendata.cloudapp.net/v1/TaipeiOGDI/StopWaterInfo', data: {format: 'json'}, dataType: 'jsonp', success: jsonpCallback }); });
就可以看到瀏覽器上顯示傳回的停水資訊中的第一筆資料的 stitle 欄位內容, 若是觀察開發人員工具中的 Network 頁次, 會看到發出的請求為:
http://taipeicityopendata.cloudapp.net/v1/TaipeiOGDI/StopWaterInfo?callback=jQuery1720631575214676559_1339854122194&format=json&_=1339854129075
你可能會覺得很奇怪, 明明我們指定的 callback 函數是 jsonpCallback, 為什麼 URL 中的 callback 參數內容卻不一樣?這是因為 jQuery 會在中間介接一個 jQurey 自動產生的函數, 在這個函數中再去呼叫我們指定的 callback 函數。
如果你要存取的 server 端不是以 "callback" 作為識別名稱, 而且你沒有辦法修改 server 端的程式, jQuery 的 ajax() 函數也提供你透過 jsonp 選項自訂參數名稱的功能, 在這種情況下, 你可以透過 jsonpCallback 選項指定 callback 函數, 此外, 還可以再透過 success 選項指定成功執行時要呼叫的 callback 函數。我們可以再把之前的範例改為這樣:
<!DOCTYPE html> <html lang="en"> <head> <title>JQuery JSONP</title> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script> <script> $(document).ready(function(){ $("#useJSONP").click(function(){ $.ajax({ url: 'http://taipeicityopendata.cloudapp.net/v1/TaipeiOGDI/StopWaterInfo', data: {format: 'json'}, dataType: 'jsonp', jsonp: 'callback', jsonpCallback: 'jsonpCallback', success: function(){ alert("success"); } }); }); }); function jsonpCallback(data){ $('#jsonpResult').text(data.d[0].stitle); } </script> </head> <body> <input type="button" id="useJSONP" value="Use JSONP"></input> <span id="jsonpResult"></span> </body> </html>
執行後除了可以看到取回的停水資訊外, 也會因為 success 選項指定的 callback 函數, 而顯示訊息視窗。如果觀察開發人員工具列中 Network 頁次, 可以看到發出的請求其 URL 為:
http://taipeicityopendata.cloudapp.net/v1/TaipeiOGDI/StopWaterInfo?callback=jsonpCallback&format=json&_=1339854333659
可以確認 jQuery 完全依照 jsonp 與 jsonpCallback 選項的值改寫了 url 選項指定的內容。透過上述的說明, 就可以利用 jQuery 進行跨網域的 ajax 存取了。