JS 跨域問題常見的五種解決方式


一、什麼是跨域?

要理解跨域問題,就先理解好概念。跨域問題是由於javascript語言安全限制中的同源策略造成的.

簡單來說,同源策略是指一段腳本只能讀取來自同一來源的窗口和文檔的屬性,這裏的同一來源指的是主機名、協議和端口號的組合.

複製代碼
URL                             說明          是否允許通信
http://www.a.com/a.js
http://www.a.com/b.js         同一域名下          允許
http://www.a.com/lab/a.js
http://www.a.com/script/b.js  同一域名下不同文件夾  允許
http://www.a.com:8000/a.js
http://www.a.com/b.js         同一域名,不同端口    不允許
http://www.a.com/a.js
https://www.a.com/b.js        同一域名,不同協議    不允許
http://www.a.com/a.js
http://70.32.92.74/b.js       域名和域名對應ip     不允許
http://www.a.com/a.js
http://script.a.com/b.js      主域相同,子域不同    不允許
http://www.a.com/a.js
http://a.com/b.js             同一域名,不同二級域名(同上) 不允許(cookie這種情況下也不允許訪問)
http://www.cnblogs.com/a.js
http://www.a.com/b.js         不同域名 不允許
複製代碼

同源策略設計之初是爲了安全,但也對正常的跨域開發造成了一定影響,不過還是有不同的解決辦法的。

 

二、解決辦法

跨域問題,更多的情況是出現在需要用ajax獲取數據時,那麼現在就先看個非跨域的栗子

(功能主要是從後臺獲取names列表,並展示出來)

前端部分:

複製代碼
<body>
    <div id="box">
        <ul>names:</ul>
    </div>
    <script type="text/javascript" src="./js/jquery.min.js"></script>
    <script type="text/javascript">
        function addContents(data){ 
            var box = document.getElementById('box'),
                ul = box.getElementsByTagName('ul')[0],
                fragment = document.createDocumentFragment(),
                li;
            for(var i=0,j=data.length; i<j; i++){ 
                li = document.createElement('li');
                if(data[i].hasOwnProperty('name')){ 
                    li.appendChild(document.createTextNode(data[i].name));
                    fragment.appendChild(li);
                }
            }
            ul.appendChild(fragment);
        }

        $.ajax({ 
            url: './cross_domain.php',
            //url: 'http://demoff.sinaapp.com/cross_domain.php',
            type: 'GET',
            dataType: 'json',
            data: {},
            success: function(data){ 
                addContents(data);
            },
            error: function(xmlHttpRequest,textStatus,error){ 
                console.log(xmlHttpRequest.status);
                console.log(textStatus);
            }
        });
    </script>
</body>
複製代碼

現在後端php是設在同域之下:

複製代碼
<?php 
    // 接收數據
    // $jsoncallback = $_GET["jsoncallback"];

    // 構造數據
    for($i=1; $i<=5; $i++){ 
        $names[] = array("name" => "name" + $i);
    }

    // $data = $jsoncallback . "(" . json_encode($names) . ")";
    
    $data = json_encode($names);
    echo $data;
?>
複製代碼

ok, 這樣一來數據可以正常加載,形如:

      

現在設置爲跨域:將ajax請求部分的url域設爲 demoff.sinaapp.com 即對換註釋部分,就會產生跨域問題

 

好那就進行解決吧

 

 

  第一: 使用 跨域資源共享(CORS)

CORS(Cross-Origin Resource Sharing)跨域資源共享,定義了必須在訪問跨域資源時,瀏覽器與服務器應該如何溝通。CORS背後的基本思想就是使用自定義的HTTP頭部讓瀏覽器與服務器進行溝通,從而決定請求或響應是應該成功還是失敗。

使用方法也很簡單,在php後端設置 Access-Control-Allow-Origin 頭即可,如:

複製代碼
<?php 
    header("Access-Control-Allow-Origin: *");
    //header("Access-Control-Allow-Origin: 我的域或ip");
    
    // 接收數據
    // $jsoncallback = $_GET["jsoncallback"];

    // 構造數據
    for($i=1; $i<=5; $i++){ 
        $names[] = array("name" => "name" + $i);
    }

    // $data = $jsoncallback . "(" . json_encode($names) . ")";
    
    $data = json_encode($names);
    echo $data;
?>
複製代碼

  

  第二:使用jsonp 

什麼是jsonp?維基百科的定義是:JSONP(JSON with Padding).

JSONP也叫填充式JSON,是應用JSON的一種新方法,只不過是被包含在函數調用中的JSON,例如:callback({"name","name1"});

JSONP由兩部分組成:回調函數和數據。回調函數是當響應到來時應該在頁面中調用的函數,而數據就是傳入回調函數中的JSON數據。

jsonp的原理是:

就是利用<script>標籤沒有跨域限制,來達到與第三方通訊的目的。

當需要通訊時,本站腳本創建一個<script>元素,地址指向第三方的API網址,並提供一個回調函數來接收數據(函數名可約定,或通過地址參數傳遞)。 
第三方產生的響應爲json數據的包裝(故稱之爲jsonp,即json padding),形如: 
callback({"name":"hax","gender":"Male"}) 
這樣瀏覽器會調用callback函數,並傳遞解析後json對象作爲參數。本站腳本可在callback函數裏處理所傳入的數據。 

(我們知道 <link href   <img src   <script src   請求的數據都不受域的限制)

jsonp的使用方法:

客戶端指明使用jsonp的方式,服務器接受參數,並外包裹要返回的數據,再一併返回。

jquery的ajax簡單描述:

前端指明data:jsonp , 在標明自定義的參數名 jsonp:jsoncallback

複製代碼
<body>
    <div id="box">
        <ul>names:</ul>
    </div>
    <script type="text/javascript" src="./js/jquery.min.js"></script>
    <script type="text/javascript">
        function addContents(data){ 
            var box = document.getElementById('box'),
                ul = box.getElementsByTagName('ul')[0],
                fragment = document.createDocumentFragment(),
                li;
            for(var i=0,j=data.length; i<j; i++){ 
                li = document.createElement('li');
                if(data[i].hasOwnProperty('name')){ 
                    li.appendChild(document.createTextNode(data[i].name));
                    fragment.appendChild(li);
                }
            }
            ul.appendChild(fragment);
        }

        $.ajax({ 
            //url: './cross_domain.php',
            url: 'http://demoff.sinaapp.com/cross_domain.php',
            type: 'GET',
            dataType: 'jsonp',
            jsonp: 'jsoncallback',
            data: {},
            success: function(data){ 
                addContents(data);
            },
            error: function(xmlHttpRequest,textStatus,error){ 
                console.log(xmlHttpRequest.status);
                console.log(textStatus);
            }
        });
    </script>
</body>
複製代碼

後端服務器部分要做的就是,拿到參數,再包裹

複製代碼
<?php 
    //header("Access-Control-Allow-Origin: *");
    //header("Access-Control-Allow-Origin: 我的域或ip");
    
    // 接收數據
     $jsoncallback = $_GET["jsoncallback"];

    // 構造數據
    for($i=1; $i<=5; $i++){ 
        $names[] = array("name" => "name" + $i);
    }

     $data = $jsoncallback . "(" . json_encode($names) . ")";
    
    //$data = json_encode($names);
    echo $data;
?>
複製代碼

結果顯示:

    

你可能會奇怪這一大串是什麼,這其實是jq自動生成的一個函數名(也就是那個jsoncallback參數的值)

其實還有一種很常見的方式就是使用 $.getJson獲取,直接給出一個網址

把$.ajax部分替換成$.getJson部分

$.getJSON('http://demoff.sinaapp.com/cross_domain.php?jsoncallback=?',function(data){ 
            addContents(data);
        });

jquery會自動生成一個全局函數來替換callback=?中的問號,之後獲取到數據後又會自動銷燬,實際上就是起一個臨時代理函數的作用。$.getJSON方法會自動判斷是否跨域,不跨域的話,就調用普通的ajax方法;跨域的話,則會以異步加載js文件的形式來調用jsonp的回調函數。

我也可以指定那個值,因爲我們目的是要運行addContents函數,那就可以直接指定爲它。不過這時就不能使用$.getJson版的匿名函數了

直接再加個<script> 看看結果,數據返回後相應的函數就被調用執行。

<script type="text/javascript" src="http://demoff.sinaapp.com/cross_domain.php?jsoncallback=addContents"></script>

    

jsonp的方式很簡便,它的缺點就是:

它只支持GET請求而不支持POST等其它類型的HTTP請求;

它只支持跨域HTTP請求這種情況,不能解決不同域的兩個頁面之間如何進行JavaScript調用的問題。

 

 

  第三: document.domain + iframe (iframe的使用主要是爲了ajax通信)

不同的框架之間是可以獲取window對象的,但卻無法獲取相應的屬性和方法。

比如,有一個頁面,它的地址是http://www.example.com/a.html ,

在這個頁面裏面有一個iframe,它的src是http://example.com/b.html,

很顯然,這個頁面與它裏面的iframe框架是不同域的,所以我們是無法通過在頁面中書寫js代碼來獲取iframe中的東西的:

複製代碼
<script type="text/javascript">
    function test(){
        var iframe = document.getElementById('ifame');
        var win = document.contentWindow;//可以獲取到iframe裏的window對象,但該window對象的屬性和方法幾乎是不可用的
        var doc = win.document;//這裏獲取不到iframe裏的document對象
        var name = win.name;//這裏同樣獲取不到window對象的name屬性
    }
</script>
<iframe id = "iframe" src="http://example.com/b.html" onload = "test()"></iframe>
複製代碼

這個時候,document.domain就可以派上用場了,

我們只要把http://www.example.com/a.html 和http://example.com/b.html這兩個頁面的document.domain都設成相同的域名就可以了。

但要注意的是,document.domain的設置是有限制的,我們只能把document.domain設置成自身或更高一級的父域,且主域必須相同。

1.在頁面 http://www.example.com/a.html 中設置document.domain:

複製代碼
<iframe id = "iframe" src="http://example.com/b.html" onload = "test()"></iframe>
<script type="text/javascript">
    document.domain = 'example.com';//設置成主域
    function test(){
        alert(document.getElementById('iframe').contentWindow);//contentWindow 可取得子窗口的 window 對象
    }
</script>
複製代碼

2.在頁面 http://example.com/b.html 中也設置document.domain:

<script type="text/javascript">
    document.domain = 'example.com';//在iframe載入這個頁面也設置document.domain,使之與主頁面的document.domain相同
</script>

上述只談到了,document.domain ,主要是爲了不同域間訪問數據操作數據。

如果想在

http://www.example.com/a.html

 頁面中通過ajax直接請求下述的頁面,可以用一個隱藏的iframe來做一個代理。

http://example.com/b.html 

原理就是讓這個iframe載入一個與你想要通過ajax獲取數據的目標頁面處在相同的域的頁面,所以這個iframe中的頁面是可以正常使用ajax去獲取你要的數據的,然後就是通過我們剛剛講得修改document.domain的方法,讓我們能通過js完全控制這個iframe,這樣我們就可以讓iframe去發送ajax請求,然後收到的數據我們也可以獲得了。

 

  第四: 使用window.name + iframe

window對象有個name屬性,該屬性有個特徵:即在一個窗口(window)的生命週期內,窗口載入的所有的頁面都是共享一個window.name的,每個頁面對window.name都有讀寫的權限,window.name是持久存在一個窗口載入過的所有頁面中的.

(簡單來看,window作爲瀏覽器端的全局對象,默認可不加,所以 也可以簡單地直接用name代替

  但name也不是簡單地充當全局變量使用。所以要注意的是,只能使用name這個屬性,使用諸如 window.name_1之類的是不行的

  我現在使用var name= 就隱式地聲明window.name了)

比如現在有兩個不同域的a.html和b.html

http://localhost:8080/demoff/a.html

複製代碼
<script type="text/javascript">
        var name = 'myNames';
        setTimeout(function(){ 
            location = 'http://demoff.sinaapp.com/b.html';
        },3000);
    </script>
複製代碼

http://demoff.sinaapp.com/b.html

<script type="text/javascript">
        alert(name);
    </script>

3秒後跳轉過去可以看到 :

數據是存在的,但實際情況中我們也不能這樣跳來跳去,所以可以用隱藏的iframe來實現數據的獲取

 

舉個荔枝:

本地的爲數據提供方:http://localhost:8080/demoff/b.html

遠程的爲數據需求方:http://demoff.sinaapp.com/b.html

則本地b.html文件:

複製代碼
<body>

    <script type="text/javascript">
        window.name = 'myName';
    </script>
</body>
複製代碼

遠程b.html文件

複製代碼
<body>
    
    <!--  一個 iframe 作爲中間件  -->
    <iframe id="myIframe" src="http://localhost:8080/demoff/a.html" style="display:none;" onload="getData()"></iframe>

    <script type="text/javascript">
        // 初始iframe加載後即執行
        function getData(){ 
            var myIframe = document.getElementById('myIframe');
            // 第一次iframe加載後即可拿到window.name值,然後設置其src爲同域的文件(隨意)
            myIframe.src = './index.php';
            // 設置好同域之後,就可以操作iframe的數據了,即可拿到該name值
            myIframe.onload = function(){ 
                var name = myIframe.contentWindow.name;
                alert(name);
            };
        }
    </script>
</body>
複製代碼

即可拿到數據

 

 

  第五:使用 window.postMessage方法

這個東西是HTML5引入的,可以在不同的window下傳遞數據,不受域的影響。目前IE8+、FireFox、Chrome、Opera等瀏覽器都已經支持該方法。

window.postMessage(message,targetOrigin) 

調用postMessage方法的window對象是指要接收消息的那一個window對象,該方法的第一個參數message爲要發送的消息,類型只能爲字符串;

第二個參數targetOrigin用來限定接收消息的那個window對象所在的域,如果不想限定域,可以使用通配符 *  。

需要接收消息的window對象,可是通過監聽自身的message事件來獲取傳過來的消息,消息內容儲存在該事件對象的data屬性中。

還是舉個栗子:(假設現在是遠程提供數據,本地獲取數據)

遠程b.html

複製代碼
<body>
    
    <!--  一個 iframe 作爲中間件  -->
    <iframe id="myIframe" src="http://localhost:8080/demoff/a.html" style="display:none;" onload="setData()"></iframe>

    <script type="text/javascript">
        // 初始iframe加載後即執行
        function setData(){ 
            var myIframe = document.getElementById('myIframe');
            var win = myIframe.contentWindow;
            //win.postMessage('My name is null','*');
            win.postMessage('My name is null','http://localhost:8080/');
        }
    </script>
</body>
複製代碼

本地b.html

複製代碼
<body>

    <script type="text/javascript">
        window.onmessage = function(e){ 
            e = e || window.event;
            alert(e.data);
        };
    </script>
</body>
複製代碼

本地即可獲取到數據

 

 

第六: 

除了上述常見的五種方法外,

還有flash方式的跨域,可參見

  http://www.cnblogs.com/sevenyuan/archive/2009/11/19/1606237.html

  http://www.2cto.com/Article/201108/100008.html

服務端也可以用一些代理的方式解決,可參見

  http://blog.csdn.net/macky0668/article/details/6247803

[-_-]眼睛累了吧,注意勞逸結合呀[-_-]
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章