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"> functionaddContents(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
等