iT邦幫忙

14

Cross Domain Ajax 跨網域抓取資料(JSONP)

divaka 2012-07-11 13:15:2153540 瀏覽

在一些瀏覽器端的語言 (例如 Javascript ) 當中,會利用 Same origin policy (同網域限制) 的概念,以保障資料傳輸的安全性,但有時候在系統設計的時候,就是會需要設計出這種系統架構,或是需要開放部份資料的 API ,來讓外部網域存取,此時就會需要 cross domain 取資料的方法,本篇文章介紹透過 JSONP 的方法,來達成跨網域取得資料的需求。
在 Wiki 對於 Same origin policy 的定義如下:

In computing, the same origin policy is an important security concept for a number of browser-side programming languages, such as JavaScript. The policy permits scripts running on pages originating from the same site to access each other’s methods and properties with no specific restrictions, but prevents access to most methods and properties across pages on different sites

中文的翻譯是,在電腦科學的領域, same origin policy 是一個重要的瀏覽器端語言的安全協定,此協定僅允許 script 在同個網域之間互相傳送資料,但是禁止不同網域之間互相取用方法與屬性。

ps. 這一份 文件裡有針對相關議題的更完整內容
http://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy

Origin determination rules

簡單來說, Same origin policy 定義了 Origin determination rules ,即特定的網域才可互相 reference,表格整理如下:

方法:JSONP

JSONP ((JSON with Padding)是解決跨網域限制的方法之一,定義如下:

JSONP 是一个非官方的协议,它允许在服务器端集成 Script tags 返回至客户端,通过 Javascript callback 的形式实现跨域访问。 來源

所以,此種方法主要是透過 JSONP 動態 (根據 XMLHttpRequest ) 產生 JSON 資料,假設我們今天想要向 http://server2.example.com/RetrieveUser?UserId=xxx 拿資料,則我們會利用瀏覽器送一段 http://server2.example.com/RetrieveUser?UserId=1823 的指令過去,該 Server 即會根據取得的 UserId = 1823 ,產生對應的 JSON response ,例如:

{"Name": "小明", "Id" : 1823, "Rank": 7}

JSONP 簡單範例

jQuery 在 1.2 版以後,就加入了對於 JSONP 的支援,只要使用 $.getJSON() 方法,即可達成跨 Domain 取得資料的實作,範例如下 (本範例從 flickr 截取資料,你可以從 Result tab 察看結果):

//從不同的 domain 拿資料 ( Server 端需使用 jsonp 格式)
$("#getDifferentDomainDataViaJsonp").click(function(){
ajaxCallJsonp("http://api.flickr.com/services/feeds/photos_public.gne?jsoncallback=?");
});

function ajaxCallJsonp(target){
    
    var data = $.getJSON(target,
    {
        tags: "mount rainier",
        tagmode: "any",
        format: "json"
    });

    //成功得到資料
    data.success(function( msg ) {
        $.each(msg.items, function(i,item){

              //empty content
              $("#result").html();


              $("#result").append($("<img/>").attr("src", item.media.m));
              
              if ( i == 3 ) {
                  return false;
              }
        });
    });

    //取得資料失敗
    data.error(function( msg ) {
        $("#result").html("fail getting data");
    });

}

以上程式碼使用 $.getJSON 方法,夾帶 tags, tagmode, format 三個參數,送到 flickr 開放的 API :http://api.flickr.com/services/feeds/photos_public.gne?jsoncallback=?

便可以取得回傳的 data ,並顯示在畫面上

URL 當中的 callback = ?

眼尖的人會注意到,透過 jsonp 的方法所傳送的 URL 當中,結尾都會帶一個問號 (?),這個問號會在取得 server 回傳值的時候,自動轉換成 call function 的 function name,而如果是一個匿名函式,則會轉換成一個帶有 time stamp 的函式名稱。

以上就是 server 端所提供的 jsonp 服務,接受 callback function 為參數,並將 callback function 的結果回傳給 client 端,當傳輸完成後,client 會自動執行 Server 回傳的 callback function,取得結果。

傳送架構圖如下:

remote Ajax call 實際範例

以下為一個實際範例:

http://design2u.me/example/remoteAjaxCall/

以上範例共有 4 個取資料的模式 (4個 Button) ,分別介紹如下

(1) 從同一個 domain 拿資料 (get same domain data)
(2) 從不同 domain 拿資料 (get different domain data)
(3) 從不同 domain 拿資料,透過 jsonp 格式 (get different domain data via jsonp)
(4) 從不同 domain 拿資料,透過 jsonp 格式 (get different domain data via jsonp)

從同一個 domain 拿資料

假設你只是要從同個網域 (或是同個專案資料夾) 下面拿資料,例如:

而 index.html 的主要 code 如下:

//從同樣的 domain 拿資料
    $("#getSameDomainData").click(function(){
        ajaxCall("jquery.min.js");
    });
 
function ajaxCall(target){
 
    //發出 ajax call
    var data = $.ajax({
        type: "POST",
        url: target,
    });
 
    //成功得到資料
    data.success(function( msg ) {
        $("#result").html(msg);
    });
 
    //取得資料失敗
    data.error(function( msg ) {
        $("#result").html("fail getting data");
    });
 
}

即可順利取得同個資料夾裡面檔案的內容,結果如下:

從不同 domain 拿資料

而假設我們同樣想要引入外部資料,但是 domain name 不同的時候呢
而現在新的程式碼如下:

    //從不同的 domain 拿資料
    $("#getDifferentDomainData").click(function(){
        ajaxCall("http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js");
    });
 
function ajaxCall(target){
 
    //發出 ajax call
    var data = $.ajax({
        type: "POST",
        url: target,
    });
 
    //成功得到資料
    data.success(function( msg ) {
        $("#result").html(msg);
    });
 
    //取得資料失敗
    data.error(function( msg ) {
        $("#result").html("fail getting data");
    });
 
}

這次改成從 jquery 的 google CDN 拿同一份文件
但是卻得到 fail getting data:

主要原因即是我們想要進行 cross domain access 遭拒絕

從 flickr 拿資料,透過 jsonp 格式

事實上許多有名的網站都有開 RESTful API,讓別人可以透過 jsonp 的方式取得資料,例如 flcikr 的相片 API
http://api.flickr.com/services/feeds/photos_public.gne?jsoncallback=?

透過 jquery 我們也可以輕易的夾帶參數送到 server ,取得對應的資料
例如以下的程式碼:

//從不同的 domain 拿資料 ( Server 端需使用 jsonp 格式)
    $("#getDifferentDomainDataViaJsonp").click(function(){
 
        var options = {
            tags: "mount rainier",
            tagmode: "any",
            format: "json"
        };
        ajaxCallJsonp("http://api.flickr.com/services/feeds/photos_public.gne?jsoncallback=?",options);
    });
 
function ajaxCallJsonp(target,options){
 
    var data = $.getJSON(target,options);
 
    //empty content
    $("#result").html("");
 
    //成功得到資料
    data.success(function( msg ) {
 
        //flickr data
        if(target=="http://api.flickr.com/services/feeds/photos_public.gne?jsoncallback=?"){
            $.each(msg.items, function(i,item){
 
              $("#result").append($("<img/>").attr("src", item.media.m));
              if ( i == 3 ) {
                return false;
              }
 
            });
        }
        //my data
        else if(target=="http://design2u.me/example/jsonp/index.php?callback=?"){
            $("#result").html('Your name is '+msg.fullname);
        }
        //other
        else{
            $("#result").html("取得資料成功,但未得到被定義的資料");
        }
 
    });
 
    //取得資料失敗
    data.error(function( msg ) {
        $("#result").html("fail getting data");
    });
 
}

讓我們可以傳送相關的參數後,帶出對應的 flikr 照片
結果如下:

從 PHP server 拿資料,透過 jsonp 格式

也可以試著自行架 jsonp server 試看看
以下範例可打造 PHP 的 jsonp server:

<?php
 $fname = $_GET['firstname'];
      if($fname=='Jeff')
      {
          //header("Content-Type: application/json");
          //組成 $_GET['callback']({'fullname':'Jeff Hansen'})
         echo $_GET['callback'] . '(' . "{'fullname' : 'Jeff Hansen'}" . ')';
 
      }
?>

Client 端的 code 如下:

//從不同的 domain 拿資料 ( Server 端需使用 jsonp 格式)
    $("#getDifferentDomainDataViaJsonp2").click(function(){
 
        var options = {
            firstname:"Jeff"
        };
 
        ajaxCallJsonp("http://design2u.me/example/jsonp/index.php?callback=?",options);
 
    });
 
function ajaxCallJsonp(target,options){
 
    var data = $.getJSON(target,options);
 
    //empty content
    $("#result").html("");
 
    //成功得到資料
    data.success(function( msg ) {
 
        //my data
        else if(target=="http://design2u.me/example/jsonp/index.php?callback=?"){
            $("#result").html('Your name is '+msg.fullname);
        }
 
    });
 
    //取得資料失敗
    data.error(function( msg ) {
        $("#result").html("fail getting data");
    });
 
}

運作的邏輯與順序為:

(1) Client 透過 $.getJSON 方法,將參數 firstname: “Jeff” 傳送過去
(2) server 接到 request,並 GET 到 firstname:”Jeff”
(3) server 確認對應的邏輯,並回傳 $_GET['callback']({‘fullname’:'Jeff Hansen’}) 給來源
(4) Client 接到 server 端給的 {‘fullname’:'Jeff Hansen’},並傳進 msg 這個變數
(5) 透過 msg.fullname ,取得 Jeff Hansen,顯示在螢幕上

從 JSP server 拿資料,透過 JSONP 格式

從 PHP 的範例可以發現,其實只要組成 $_GET['callback']({‘fullname’:'Jeff Hansen’}) 這樣的回傳格式, 即可將資料吐回 client 端,所以 JSP Server 只要透過以下語法,也可以組成合適的格式:

/**
 *
 * @param form
 * @param request
 * @param response
 * @return
 * @throws Exception
 */
public String ajaxGetMybusAllDataJsonp(ActionForm form,
        HttpServletRequest request, HttpServletResponse response)
        throws Exception {
 
   // 取得 client 端傳來的參數
    String callback = request.getParameter("callback");
 
    //宣告 JSON 物件
    JSONObject res = new JSONObject();
    res.put("item", "item content");
    res.put("item2", "item content2");
 
    // 避免 browser 出現亂碼
    response.setContentType("text/html");
    response.setCharacterEncoding("utf-8");
    PrintWriter out = response.getWriter();
 
  // 組成 callback 格式
  out.print(callback + "(" + res.toString() + ")");
    out.flush();
    out.close();
    return null;
}

範例下載

以上的幾個範例,可在以下連結 下載到
http://design2u.me/example/remoteAjaxCall/remoteAjaxCall.rar


圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中
0
ted99tw
iT邦高手 1 級 ‧ 2012-07-11 16:39:09

歐,大哥!!這太太太................重量級了吧!!不推不行!!拍照

0
player
iT邦大師 1 級 ‧ 2012-07-11 18:53:56

基於安全性
跨網域的ajax通常會被瀏覽器檔掉

通常是在同一個網域內多寫隻 proxy 的code
先透過同一個網域內的 server 去抓遠端資料
再傳給用戶端

您好,我最近也是有遇到這個問題,但嘗試了很多次jsonp的方法都無法解決,想嘗試看看寫proxy解決,但沒有個頭緒怎麼寫,請問我可以去哪裡看些範例,或者是可以請教您呢?

0
jyyihch
iT邦新手 5 級 ‧ 2012-07-14 19:09:28

很有幫助,謝謝大大無私的分享謝謝

0
ctc316
iT邦新手 5 級 ‧ 2017-01-11 15:38:59

感謝作者大大,解釋相當清楚易懂!

我要留言

立即登入留言