你不知道的JavaScript--Item33 跨域總結與解決的方法

1、神馬是跨域(Cross Domain)

說白點就是post、get的url不是你當前的站點,域名不一樣。好比在*aaa.com/a.html*裏面,表單的提交action是bbb.com/b.htmljavascript

不只如此,www.aaa.comaaa.com之間也屬於跨域。因爲www.aaa.com是二級域名,aaa.com是根域名。php

JavaScript出於安全方面的考慮,是不一樣意跨域調用其它頁面的對象的(同源策略 Same-Origin Policy)。css

這裏寫圖片描寫敘述

特別注意兩點:html

  • 第一。假設是協議和端口形成的跨域問題「前臺」是無能爲力的。
  • 第二:在跨域問題上。域僅僅是經過「URL的首部」來識別而不會去嘗試推斷一樣的ip地址相應着兩個域或兩個域是否在同一個ip上。

    「URL的首部」指window.location.protocol +window.location.host,也可以理解爲「Domains, protocols and ports must match」。html5

2、爲嘛要跨域

跨域這東西事實上非常常見,好比咱們可以把站點的一些腳本、圖片或其它資源放到另一個站點。java

好比咱們可以使用Google提供的jQuery,載入時間少了,並且下降了server的流量,例如如下:jquery

<script type="text/java script" src="https://aja x.googleapis.com/aj ax/libs/jquery/1.4.2/jquery.min.js"></script>

跨域問題產生的場景web

有時候不只僅是一些腳本、圖片這樣的資源,咱們也會但願從另外的站點調用一些數據(有時候是不得不這樣)。好比我但願獲取一些blog的RSS來生成一些內容,再或者說我在「人人開放平臺」上開發一個應用。需要調用人人的數據。ajax

當要在在頁面中使用js獲取其它站點的數據時。就會產生跨域問題,比方在站點中使用ajax請求其它站點的天氣、快遞或者其它數據接口時以及hybrid app中請求數據,瀏覽器就會提示如下錯誤。chrome

這樣的場景下就要解決js的跨域問題。
然而,很是不幸的是,直接用XMLHttpRequest來Get或者Post是不行的。好比我用jQuery的$.get去訪問例如如下主域名 :

$.get("http://flycoder.org/",
{}, function(data){
alert('跨域不是越獄:'+data)
}, "html");

結果例如如下(總之就是不行啦~FF不報錯,但是木有返回數據):

這裏寫圖片描寫敘述

那咋麼辦捏?(弱弱的說,測試的時候我發現IE訪問本地文件時,是可以跨域的,只是這也沒啥用~囧~)

3、腫麼跨域

在瀏覽器中,<script><img><iframe><link>這幾個標籤是可以載入跨域(非同源)的資源的,並且載入的方式事實上至關於一次普通的GET請求,惟一不一樣的是,爲了安全起見,瀏覽器不一樣意這樣的方式下對載入到的資源的讀寫操做,而僅僅能使用標籤自己應當具有的能力(比方腳本運行、樣式應用等等)。


最多見的跨域問題是Ajax跨域訪問的問題,默認狀況下,跨域的URL是沒法經過Ajax訪問的。這裏我記錄我所瞭解到的跨域的方法:

  1. server端代理,這沒有什麼可說的。缺點在於,默認狀況下接收Ajax請求的服務端是沒法獲取到的client的IP和UA的。

  2. iframe,使用iframe事實上至關於開了一個新的網頁,詳細跨域的方法大體是,域A打開的母頁面嵌套一個指向域B的iframe,而後提交數據。完畢以後,B的服務端可以:
    ●返回一個302重定向響應,把結果又一次指回A域;
    ●在此iframe內部再嵌套一個指向A域的iframe。

這二者都終於實現了跨域的調用。這種方法功能上要比如下介紹到的JSONP更強。因爲跨域完畢以後DOM操做和互相之間的JavaScript調用都是沒有問題的。但是也有一些限制,比方結果要以URL參數傳遞,這就意味着在結果數據量很是大的時候需要切割傳遞,甚是麻煩;另外一個麻煩是iframe自己帶來的。母頁面和iframe自己的交互自己就有安全性限制。

三、 利用script標籤跨域,這個辦法也非常常見。script標籤是可以載入異域的JavaScript並運行的,經過預先設定好的callback函數來實現和母頁面的交互。它有一個大名。叫作JSONP跨域。JSONP是JSON with Padding的略稱。

它是一個非官方的協議,明明是載入script,爲啥和JSON扯上關係呢?原來就是這個callback函數。對它的使用有一個典型的方式,就是經過JSON來傳參,即將JSON數據填充進回調函數,這就是JSONP的JSON+Padding的含義。如下詳細介紹一下。

爲了更好的解說和測試,咱們可以經過改動hosts文件來模擬跨域的效果。hosts文件在C:\Windows\System32\drivers\etc 目錄下。在如下加3行:

127.0.0.1 www.a.com

127.0.0.1 a.com

127.0.0.1 www.b.com

3.一、跨域代理

一種簡單的辦法。就是把跨域的工做交給server。從後臺獲取其它站點的數據再返回給前臺,也就是跨域代理(Cross Domain Proxy)。

這樣的方法彷佛蠻簡單的。改動也不太大。只是就是http請求多了些,響應慢了些。server的負載重了些~

這裏寫圖片描寫敘述

3.二、document.domain+iframe的設置

對於主域一樣而子域不一樣的樣例,可以經過設置document.domain的辦法來解決。

舉www.a.com/a.html和a.com/b.html爲例.

思路:僅僅需在a.html中加入一個b.html的iframe,並且設置兩個頁面的document.domain都爲’a.com’(僅僅能爲主域名)。兩個頁面之間便可互相訪問了。代碼例如如下:

www.a.com/a.html中的script

<!DOCTYPE HTML>
<html>
<head>
    <meta name="name" content="content" charset="utf-8">
</head>
<body>
<script type="text/javascript"> document.domain='a.com'; var ifr = document.createElement('iframe'); ifr.src = 'http://a.com/b.html'; ifr.style.display = 'none'; document.body.appendChild(ifr); ifr.onload = function(){ //獲取iframe的document對象 //W3C的標準方法是iframe.contentDocument, //IE六、7可以使用document.frames[ID].document //爲了更好兼容,可先獲取iframe的window對象iframe.contentWindow var doc = ifr.contentDocument || ifr.contentWindow.document; // 在這裏操縱b.html alert(doc.getElementById("test").innerHTML); }; </script>
</body>
</html>

備註:某一頁面的domain默認等於window.location.hostname。

主域名是不帶www的域名,好比a.com。主域名前面帶前綴的一般都爲二級域名或多級域名,好比www.a.com事實上是二級域名。

domain僅僅能設置爲主域名。不可以在b.a.com中將domain設置爲c.a.com。

a.com/b.html

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title></title>
<script type="text/javascript"> document.domain='a.com'; </script>
</head>
<body>
<h1 id="test">Hello World</h1>
</body>
</html>

這裏寫圖片描寫敘述

假設b.html要訪問a.html,可在子窗體(iframe)中經過window.parent來訪問父窗體的window對象,而後就可以隨心所欲了(window對象都有了,還有啥不行的),同理子窗體也可以和子窗體之間通訊。

因而,咱們可以經過b.html的XMLHttpRequest來獲取數據,再傳給a.html。從而解決跨子域獲取數據的問題。

但是這樣的方法僅僅支持同一根域名下的頁面。假設不一樣根域名(好比baidu.com想訪問google.com)那就無能爲力了。

問題:

  • 一、安全性,當一個站點(b.a.com)被攻擊後,另外一個站點(c.a.com)會引發安全漏洞。

  • 二、假設一個頁面中引入多個iframe,要想可以操做所有iframe,必須都得設置一樣domain。

3.三、動態script標籤(Dynamic Script Tag)

儘管瀏覽器默認禁止了跨域訪問。但並不由止在頁面中引用其它域的JS文件,並可以自由運行引入的JS文件裏的function(包含操做cookie、Dom等等)。依據這一點。可以方便地經過建立script節點的方法來實現全然跨域的通訊。

這樣的方法也叫「動態腳本注入」。

這樣的技術克服了XMLHttpRequest的最大限制,也就是跨域請求數據。直接用JavaScript建立一個新的腳本標籤,而後設置它的src屬性爲不一樣域的URL。

www.a.com/a.html中的script

var dynScript = document.createElement('script');
dynScript.src = 'http://www.b.com/b.js';
dynScript.setAttribute("type", "text/javascript");
document.getElementsByTagName('head')[0].appendChild(dynScript);

經過動態標籤注入的必須是可運行的JavaScript代碼,所以無論是你的數據格式是啥(xml、json等),都必須封裝在一個回調函數中。一個回調函數例如如下:

www.a.com/a.html中的script

function dynCallback(data){
  //處理數據, 此處簡單示意一下
  alert(data.content);
}

在這個樣例中。www.b.com/b.js需要將數據封裝在上面這個dynCallback函數中。例如如下:

dynCallback({content:'來自b.com/b.js的消息Hello World!'});

咱們看到了讓人開心的結果。Hello World~

這裏寫圖片描寫敘述

只是動態腳本注入仍是存在很多問題的,如下咱們拿它和XMLHttpRequest來對照一下:

這裏寫圖片描寫敘述

可以看出,動態腳本注入仍是有很多限制,僅僅能使用Get。不能像XHR同樣推斷Http狀態等。

並且使用動態腳本注入的時候必須注意安全問題。因爲JavaScript沒有不論什麼權限與訪問控制的概念。經過動態腳本注入的代碼可以全然控制整個頁面。因此引入外部來源的代碼必須多加當心。

3.4 利用iframe和location.hash

這個辦法比較繞,但是可以解決全然跨域狀況下的腳步置換問題。原理是利用location.hash來進行傳值。

  • ww.a.com下的a.html想和www.b.com下的b.html通訊(在a.html中動態建立一個b.html的iframe來發送請求)。

  • 但是因爲「同源策略」的限制他們沒法進行交流(b.html沒法返回數據),因而就找個中間人:www.a.com下的c.html(注意是www.a.com下的);

  • b.html將數據傳給c.html(b.html中建立c.html的iframe)。因爲c.html和a.html同源,因而可經過c.html將返回的數據傳回給a.html。從而達到跨域的效果。

這裏寫圖片描寫敘述

三個頁面之間傳遞參數用的是location.hash(也就是www.a.html#sayHello後面的’#sayHello’)。改變hash並不會致使頁面刷新(這點很是重要)。

詳細代碼例如如下:

www.a.com/a.html

//經過動態建立iframe的hash發送請求 function sendRequest(){ var ifr = document.createElement('iframe'); ifr.style.display = 'none'; //跨域發送請求給b.html, 參數是sayHello ifr.src = 'http://www.b.com/b.html#sayHello'; document.body.appendChild(ifr); } //獲取返回值的方法 function checkHash() { var data = location.hash ?

location.hash.substring(1) : ''; if (data) { //處理返回值 alert(data); location.hash=''; } } //定時檢查本身的hash值 setInterval(checkHash, 2000); window.onload = sendRequest;

www.b.com/b.html

function checkHash(){
  var data = '';
  //模擬一個簡單的參數處理操做
  switch(location.hash){
    case '#sayHello': data = 'HelloWorld';break;
    case '#sayHi': data = 'HiWorld';break;
    default: break;
  }
  data && callBack('#'+data);
}
function callBack(hash){
  // ie、chrome的安全機制沒法改動parent.location.hash,
  // 因此要利用一箇中間的www.a.com域下的代理iframe
  var proxy = document.createElement('iframe');
  proxy.style.display = 'none';
  // 注意該文件在"www.a.com"域下
  proxy.src = 'http://www.a.com/c.html'+hash;
  document.body.appendChild(proxy);
}
window.onload = checkHash;

www.a.com/c.html

//因爲c.html和a.html屬於同一個域。
//因此可以改變其location.hash的值
//可經過parent.parent獲取a.html的window對象
parent.parent.location.hash = self.location.hash.substring(1);

這裏寫圖片描寫敘述

可能有人會有疑問。既然c.html已經獲取了a.html的window對象了。爲什麼不直接改動它的dom或者傳遞參數給某個變量呢?

緣由是在c.html中改動 a.html的dom或者變量會致使頁面的刷新。a.html會又一次訪問一次b.html,b.html又會訪問c.html。形成死循環……囧呀~

因此僅僅能經過location.hash了。這樣作也有些很差的地方,諸如數據容量是有限的(受url長度的限制),並且數據暴露在url中(用戶可以任意改動)……

3.五、postMessage(html5)

HTML5中最酷的新功能之中的一個就是 跨文檔消息傳輸Cross Document Messaging。下一代瀏覽器都將支持這個功能:Chrome 2.0+、Internet Explorer 8.0+, Firefox 3.0+, Opera 9.6+, 和 Safari 4.0+ 。

Facebook已經使用了這個功能,用postMessage支持基於web的實時消息傳遞。

otherWindow.postMessage(message, targetOrigin);

  • otherWindow: 對接收信息頁面的window的引用。可以是頁面中iframe的contentWindow屬性;window.open的返回值;經過name或下標從window.frames取到的值。

  • message: 所要發送的數據,string類型。
  • targetOrigin: 用於限制otherWindow,「*」表示不做限制
  • a.com/index.html中的代碼:
<iframe id="ifr" src="http://www.b.com/b.html"></iframe>
<script> window.onload = function() { var ifr = document.getElementById('ifr'); // 若寫成'http://www.c.com'就不會運行postMessage了 var targetOrigin = 'http://www.b.com'; ifr.contentWindow.postMessage('sayHello', targetOrigin); };

b.com/b.html中的代碼:

//經過message事件來通訊,實在太爽了
window.addEventListener('message', function(e){
  // 經過origin屬性推斷消息來源地址
  if (e.origin == 'http://www.a.com' &&
    e.data=='sayHello') {
    alert('Hello World');
  }
}, false);

這裏寫圖片描寫敘述

3.5 使用window.name來進行跨域

window對象有個name屬性。該屬性有個特徵:即在一個窗體(window)的生命週期內,窗體載入的所有的頁面都是共享一個window.name的,每個頁面對window.name都有讀寫的權限。window.name是持久存在一個窗體載入過的所有頁面中的,並不會因新頁面的載入而進行重置。

比方:有一個頁面www.a.com/a.html它裏面有這樣的代碼:

<script type="text/javascript"> window.name = "我是a.html的window.name"; setTimeout(function(){ window.location = 'b.html'; },3000);

再看看www.a.com/b.html頁面的代碼:

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

這裏寫圖片描寫敘述

咱們看到在b.html頁面上成功獲取到了它的上一個頁面a.html給window.name設置的值。假設在以後所有載入的頁面都沒對window.name進行改動的話,那麼所有這些頁面獲取到的window.name的值都是a.html頁面設置的那個值。

固然,假設有需要,其中的不論什麼一個頁面都可以對window.name的值進行改動。

注意,window.name的值僅僅能是字符串的形式。這個字符串的大小最大能贊成2M左右甚至更大的一個容量。詳細取決於不一樣的瀏覽器,但一般是夠用了。

上面的樣例中。咱們用到的頁面a.html和b.html是處於同一個域的,但是即便a.html與b.html處於不一樣的域中。上述結論一樣是適用的,這也正是利用window.name進行跨域的原理。

如下就來看一看詳細是怎麼樣經過window.name來跨域獲取數據的。仍是舉例說明。

比方有一個www.a.com/a.html頁面,需要經過a.html頁面裏的js來獲取另外一個位於不一樣域上的頁面www.b.com/b.html裏的數據。

b.html頁面裏的代碼很是easy,就是給當前的window.name設置一個a.html頁面想要獲得的數據值。b.html裏的代碼:

<script type="text/javascript"> window.name ="我就是頁面a.html想要的數據,所有可以轉化成字符串的數據都可以在這裏使用。比方一個json數據"; </script>

那麼在a.html頁面中,咱們怎麼把data.html頁面載入進來呢?顯然咱們不能直接在a.html頁面中經過改變window.location來載入data.html頁面,因爲咱們想要即便a.html頁面不跳轉也能獲得data.html裏的數據。答案就是在a.html頁面中使用一個隱藏的iframe來充當一箇中間人角色,由iframe去獲取data.html的數據,而後a.html再去獲得iframe獲取到的數據。

充其中間人的iframe想要獲取到data.html的經過window.name設置的數據。僅僅需要把這個iframe的src設爲www.cnblogs.com/data.html便可了。而後a.html想要獲得iframe所獲取到的數據,也就是想要獲得iframe的window.name的值。還必須把這個iframe的src設成跟a.html頁面同一個域才行,否則依據前面講的同源策略,a.html是不能訪問到iframe裏的window.name屬性的。這就是整個跨域過程。

看下a.html頁面的代碼:

<script type="text/javascript"> function getData(){ var ifr = document.getElementById('proxy'); ifr.onload = function(){//這個時候a.html與ifr已是同源了,可以相互訪問 var data= ifr.contentWindow.name;//獲取iframe裏的數據,也就是data.html頁面設置的數據 alert(data);//成功得到了數據。 } ifr.src='about:blank';//這裏的about:blank可以是隨便的一個頁面,僅僅要與a.html同源就可以,目的是讓a.html可以訪問到iframe裏的數據。

} </script> <iframe id="proxy" src="http://www.b.com/b.html" style="display: none" onload="getData()"></iframe>

這裏寫圖片描寫敘述

上面的代碼僅僅是最簡單的原理演示代碼。你可以對使用js封裝上面的過程,比方動態的建立iframe,動態的註冊各類事件等等,固然爲了安全,獲取完數據後,還可以銷燬做爲代理的iframe。網上也有很是多類似的現成代碼。有興趣的可以去找一下。

經過window.name來進行跨域,就是這樣子的。

3.6 經過jsonp跨域

在js中,咱們直接用XMLHttpRequest請求不一樣域上的數據時。是不可以的。但是,在頁面上引入不一樣域上的js腳本文件倒是可以的,jsonp正是利用這個特性來實現的。

json≠jsonp

原理
jsonp解決跨域問題的原理是,瀏覽器的script標籤是不受同源策略限制(你可以在你的網頁中設置script的src屬性問cdnserver中靜態文件的路徑)。那麼就可以使用script標籤從server獲取數據,請求時加入一個參數爲callbakc=?,?號時你要運行的回調方法。
比方,有個www.a.com/a.html頁面。它裏面的代碼需要利用ajax獲取一個不一樣域上的json數據。假設這個json數據地址是http://www.b.com/b.php,那麼a.html中的代碼就可以這樣:

<script type="text/javascript"> function dosomething(jsondata){ //處理json數據 } </script>
<script src="http://www.b.com/b.php?callback=dosomething"></script>

咱們看到獲取數據的地址後面另外一個callback參數,按慣例是用這個參數名,但是你用其它的也同樣。

固然假設獲取數據的jsonp地址頁面不是你本身能控制的,就得依照提供數據的那一方的規定格式來操做了。

因爲是當作一個js文件來引入的。因此http://www.b.com/b.php返回的必須是一個能運行的js文件,因此這個頁面的php代碼多是這樣的:

<?php $callback = $_GET['callback'];//獲得回調函數 $data = array('a','b','c','d');//要返回的數據 echo $callback.'('.json_encode($data).')';//輸出 ?>

終於那個頁面輸出的結果是:

這裏寫圖片描寫敘述

因此經過http://www.b.com/b.php?callback=dosomething獲得的js文件,就是咱們以前定義的dosomething函數,並且它的參數就是咱們需要的json數據,這樣咱們就跨域得到了咱們需要的數據。

這樣jsonp的原理就很是清楚了,經過script標籤引入一個js文件,這個js文件載入成功後會運行咱們在url參數中指定的函數,並且會把咱們需要的json數據做爲參數傳入。因此jsonp是需要server端的頁面進行相應的配合的。

知道jsonp跨域的原理後咱們就可以用js動態生成script標籤來進行跨域操做了,而不用特地的手動的書寫那些script標籤。假設你的頁面使用jquery,那麼經過它封裝的方法就能很是方便的來進行jsonp操做了。

<script type="text/javascript"> $getJSON('http://www.b.com/b.php?callback=?',function(jsondata){ //處理得到的json數據; });

原理是同樣的,僅僅只是咱們不需要手動的插入script標籤以及定義回掉函數。

jquery會本身主動生成一個全局函數來替換callback=?

中的問號。以後獲取到數據後又會本身主動銷燬,實際上就是起一個暫時代理函數的做用。$.getJSON方法會本身主動推斷是否跨域。不跨域的話。就調用普通的ajax方法。跨域的話,則會以異步載入js文件的形式來調用jsonp的回調函數。

4、總結

研究了幾天,儘管對多種跨域方法都有所瞭解了,但是真要投入應用仍是明顯不夠的(仍是需要藉助一些js庫)。

每種方法都有其優缺點,使用的時候事實上應該將多種跨域方法進一步封裝一下,統一調用的接口,利用js來本身主動推斷哪一種方法更爲適用 。

相關文章
相關標籤/搜索