父子頁面之間跨域通訊的方法

因爲同源策略的限制,Javascript跨域的問題,一直是一個比較棘手的問題,爲了解決頁面之間的跨域通訊,你們煞費苦心,研究了各類跨域方案。深刻了解以後,這裏給你們介紹一點個人具體作法。javascript

先來看看哪些狀況下才存在跨域的問題:html

 

編號html5

URLjava

說明chrome

是否容許通訊跨域

1瀏覽器

http://www.a.com/a.js安全

http://www.a.com/b.js服務器

同一域名下cookie

容許

2

http://www.a.com/lab/a.js

http://www.a.com/script/b.js

同一域名下不一樣文件夾

容許

3

http://www.a.com:8000/a.js

http://www.a.com/b.js

同一域名,不一樣端口

不容許

4

http://www.a.com/a.js

https://www.a.com/b.js

同一域名,不一樣協議

不容許

5

http://www.a.com/a.js

http://70.32.92.74/b.js

域名和域名對應ip

不容許

6

http://www.a.com/a.js

http://script.a.com/b.js

主域相同,子域不一樣

不容許

7

http://www.a.com/a.js

http://a.com/b.js

同一域名,不一樣二級域名(同上)

不容許(cookie這種狀況下也不容許訪問)

8

http://www.a.com/a.js

http://www.b.com/b.js

不一樣域名

不容許

 

 

其中編號67兩種狀況同屬於主域名相同的狀況,能夠設置domain來解決問題,今天就不討論這種狀況了。

對於其餘跨域通訊的問題,我想又能夠分紅兩類,其一(第一種狀況)a.com下面的a.js試圖請求b.com下某個接口時產生的跨域問題。其二(第二種狀況)是當a.comb.com下面的頁面成父子頁面關係時試圖互相通訊時產生的跨域問題,典型的應用場景如a.com/a.html使用iframe內嵌了b.com/b.html,你們都知道a.html內的js腳本試圖訪問b.html時是會被拒絕的,反之亦然。

第一種狀況,目前主流的方案是JSONP,高版本瀏覽器支持html5的話,還可使用XHR2支持跨域通訊的新特性。

第二種狀況,目前主要是經過代理頁面或者使用postMessageAPI來作,這也是今天要討論的話題。

 

第二種狀況,有這樣一些相似的案例:a.com/a.html使用iframe內嵌了b.com/b.html,如今但願iframe的高度能自動適應b.html的高度,使iframe不要出現滾動條。咱們都知道跨域了,a.html是沒辦法直接讀取到b.html的高度的,b.html也沒辦法把本身的高度告訴a.html

直接說能夠用代理頁面的方法搞定這個問題吧,可是怎麼代理法,先來看下面這張圖:

 

1

b.htmla.html是不能直接通訊的。咱們能夠在b.html下面再iframe內嵌一個proxy.html頁面,由於這個頁面是放在a.com下面的,與a.html同域,因此它實際上是能夠和a.html直接通訊的,假如a.html裏面有定義一個方法_callback,在proxy.html能夠直接top._callback()調用它。可是b.html自己和proxy.html也是不能直接通訊的,所謂代理頁面的橋樑做用怎麼實現呢?

b.html內嵌proxy.html是經過一段相似下面這樣的代碼:

<iframe id="proxy" src="a.com/proxy.html" name="proxy" frameborder="0" width="0" height="0"></iframe>

這個iframesrc屬性b.html是有權限控制的。若是它把src設置成a.com/proxy.html?args=XXX,也就是給url加一個查詢字符串,proxy.html內的js是能夠讀取到的。對的,這個url的查詢字符串就是b.htmlproxy.html之間通訊的橋樑,美中不足的是每次通訊都要重寫一次url形成一次網絡請求,這有時會對服務器及頁面的運行效率產生很大的影響。同時因爲參數是經過url來傳遞的,會有長度和數據類型的限制,蒐集的資料顯示:

ØIE瀏覽器對URL的長度現限制爲2048字節。

Ø360極速瀏覽器對URL的長度限制爲2118字節。

ØFirefox(Browser)URL的長度限制爲65536字節。

ØSafari(Browser)URL的長度限制爲80000字節。

ØOpera(Browser)URL的長度限制爲190000字節。

ØGoogle(chrome)URL的長度限制爲8182字節。

上面的方法,經過迂迴戰術實現了b.htmla.html通訊,可是倒過來,a.html怎麼跟b.html通訊呢?嵌入在b.html裏面的proxy.html能夠用top快速的聯繫上a.html,可是要想讓a.html找到proxy.html就不容易了,夾在中間的b.html生生把它們分開了,a.html無法讓b.html去找到proxy.html而後返回給它。只能採用更迂迴的戰術了。

順着前面b.htmla.html的通訊過程,逆向的想一下,雖然a.html沒有辦法主動找到proxy.html,可是proxy.html能夠反過來告訴a.html它在哪裏:

proxy.html加這麼一段腳本:

<scripttype="text/javascript">

var topWin = top;

function getMessage(data) {

alert("messageFormTopWin:" + data);

}

 

function sendMessage(data) {

topWin.proxyWin = window;

topWin.getMessage(data);

}

 

</script>

 

a.html加這麼一段腳本:

<scripttype="text/javascript">

var proxyWin = null;

function getMessage(data) {

alert("messageFormProxyWin:"+data);

sendMessage("top has receive data:"+data);

}

 

function sendMessage(data) {

if (null != proxyWin) {

proxyWin.getMessage(data);

}

}

</script>

也就是必須由proxy.html先主動發送一個消息給a.htmla.html獲得proxy.html頁面window的引用,就能夠反過來向它發送請求了。

如今a.html能夠把消息發給proxy.html了,可是proxy.html怎麼把消息轉送到b.html?彷佛這纔是難點,由於它們之間才真正有着「跨域」這一道鴻溝。

這回咱們再也不用前面那個iframe內嵌代理頁面的方法再在proxy.html內嵌一個b.com下面的代理頁面了,這樣實在會給人感受嵌的太深了,四層。可是爲了跨越這道鴻溝,b.com下面也加一個代理頁面是免不的。不過如今咱們要利用一下window.namewindow.name有一個特性,就是頁面在同一個瀏覽器窗口(標籤頁)中跳轉時,它一直存在並且值不會改變。好比咱們在a.html中設置了window.name=」a」,而後location.href=」http://b.com/b.html跳轉後,b.html能夠讀取window.name的值爲」a」;並且window.name的值長度通常能夠到達2Miefirefox甚至能夠達到32M,這樣的存儲容量,足夠利用起來作跨域的數據傳遞了。好吧,咱們如今要作的就是當proxy.html拿到a.html發送過來的數據後把這個數據寫入window.name中,而後跳轉到b.com下面的代理頁面,咱們這裏假設是bproxy.htmlbproxy.html讀取到window.name值後,通知給它父頁面b.html就簡單了。咱們再來看這個過程能夠用圖大概示意一下:

 

2

圖例中綠色的雙向箭頭表示能夠通訊,橙色的雙向箭頭表示不能直接通訊。

最後咱們簡單看一下雙向通訊的實測效果:

 

3

b.html每次加載的時候都先給a.html發一個鏈接請求,讓a.html能夠找到proxy.html。因此頁面第一次加載的時候會產生三個請求:

4

每次b.htmla.html發送消息的時候會產生一個請求:

5

每次a.htmlb.html發送消息的時候會產生兩個請求,其中一個是a.com/proxy.htmlb.com/bproxy.html跳轉產生的,另外一個是b.html從新向a.html發起「鏈接請求」時產生的:

6

最後簡單看一下實測的幾個測試頁面代碼:

代碼片斷一,a.com/a.html:

<htmlxmlns="http://www.w3.org/1999/xhtml">

<head>

<title>a.com</title>

</head>

<body>

<divid="Div1">

A.com/a.html</div>

<inputid="txt_msg"type="text"/>

<inputid="Button1"type="button"value="b.com/b.html發送一條消息"onclick="sendMessage(document.getElementById('txt_msg').value)"/>

<divid="div_msg">

</div>

<iframewidth="800"height="400"id="mainFrame"src="http://localhost:8091/b.com/b.htm">

</iframe>

<scripttype="text/javascript">

var proxyWin = null;

function showMsg(msg) {

document.getElementById("div_msg").innerHTML = msg;

}

 

function getMessage(data) {

showMsg("messageForm b.html to ProxyWin:" + data);

}

 

function sendMessage(data) {

if (null != proxyWin) {

proxyWin.getMessage(data);

}

}

</script>

</body>

</html>

 

代碼片斷二,a.com/proxy.html:

<htmlxmlns="http://www.w3.org/1999/xhtml">

<head>

<title>a.com</title>

</head>

<body>

<divid="Div1">A.com/proxy.html</div>

<divid="div_msg"></div>

<scripttype="text/javascript">

var topWin = top;

function showMsg(msg) {

document.getElementById("div_msg").innerHTML = msg;

}

 

function getMessage(data) {

showMsg("messageForm A.com/a.html:" + data + "<br/>B.com/bproxy.html");

window.name = data;

setTimeout(function () { location.href = "http://localhost:8091/b.com/bproxy.htm" }, 2000);//爲了能讓你們看到跳轉的過程,因此加了個延時

}

 

function sendMessage(data) {

topWin.proxyWin = window;

topWin.getMessage(data);

}

 

var search = location.search.substring(1);

showMsg("messageForm B.com/b.html:" + search);

sendMessage(search);

</script>

</body>

</html>

 

代碼片斷三,b.com/b.html

<htmlxmlns="http://www.w3.org/1999/xhtml">

<head>

<title>b.com</title>

</head>

<body>

<divid="Div1">

B.com/b.html</div>

<inputid="txt_msg"type="text"/>

<inputid="Button1"type="button"value="A.com/a.html發送一條消息"onclick="sendMessage(document.getElementById('txt_msg').value)"/>

<divid="div_msg">

</div>

<iframeid="proxy"name="proxy"style="width: 600px; height: 300px"></iframe>

<scripttype="text/javascript">

function showMsg(msg) {

document.getElementById("div_msg").innerHTML = msg;

}

function sendMessage(data) {

var proxy = document.getElementById("proxy");

proxy.src = "about:blank";

proxy.src="http://localhost:8090/a.com/proxy.htm?data=" + data;

}

function connect() {

sendMessage("connect");

}

function getMessage(data) {

showMsg("messageForm a.html to ProxyWin:" + data);

connect();

}

 

connect(); //頁面一加載,就執行一次鏈接

</script>

</body>

</html>

 

代碼片斷四,b.com/bproxy.html

<htmlxmlns="http://www.w3.org/1999/xhtml">

<head>

<title>b.com</title>

</head>

<body>

<divid="Div1">

B.com/bproxy.html</div>

<divid="div_msg">

</div>

<scripttype="text/javascript">

var parentWin = parent;

var data = null;

 

function getMessage() {

if (window.name) {

data = window.name;

parentWin.getMessage(data);

}

document.getElementById("div_msg").innerHTML = "messageForm a.com/proxy.html:" + data;

 

}

getMessage();

</script>

</body>

</html>

 

好吧,如今我必須把話鋒調轉一下了。前面講的這麼多,也只是拋出來一些以前咱們可能會採用的跨域通訊方法,事實上代理頁面、url傳參數和window.name、甚至還有一些利用urlhash值的跨域傳值方法,都能百度到很多相關資料。但它們都逃不開代理頁面,也就不可避免的要產生網絡請求,而事實上這並非咱們的本意,咱們本來但願它們可以直接在客戶端通訊,避免沒必要要的網絡請求開銷——這些開銷,在訪問量超大的站點可能會對服務器產生至關大的壓力。那麼,有沒有更完美一點的替代方案呢?

必須給你們推薦postMessagepostMessage 正是爲了知足一些合理的、不一樣站點之間的內容能在瀏覽器端進行交互的需求而設計的。利用postMessage API實現跨域通訊很是簡單,咱們直接看一下實例的代碼:

代碼片斷一,A.com/a.html

<htmlxmlns="http://www.w3.org/1999/xhtml">

<headrunat="server">

<title>A.com/a.html</title>

<scripttype="text/javascript">

var trustedOrigin = "http://localhost:8091";

 

function messageHandler(e) {

if (e.origin == trustedOrigin) {//接收消息的時候,判斷消息是否來自可信的源,這個源是否可信則徹底看本身的定義了。

showMsg(e.data);//e.data纔是真實要傳遞的數據

} else {

// ignore messages from other origins

}

}

 

function sendString(s) {//發送消息

document.getElementById("widget").contentWindow.postMessage(s, trustedOrigin);

}

 

function showMsg(message) {

document.getElementById("status").innerHTML = message;

}

 

function sendStatus() {

var statusText = document.getElementById("statusText").value;

sendString(statusText);

}

 

function loadDemo() {

addEvent(document.getElementById("sendButton"), "click", sendStatus);

sendStatus();

}

 

function addEvent(obj, trigger, fun) {

if (obj.addEventListener) obj.addEventListener(trigger, fun, false);

elseif (obj.attachEvent) obj.attachEvent('on' + trigger, fun);

else obj['on' + trigger] = fun;

}

addEvent(window, "load", loadDemo);

addEvent(window, "message", messageHandler);

 

</script>

</head>

<body>

<h1>A.com/a.html</h1>

<p><b></b>: http://localhost:8090</p>

<inputtype="text"id="statusText"value="msg from a.com/a.html">

<buttonid="sendButton">b.com/b.html發送消息</button>

<p>接收到來自a.com/a.html的消息: <strongid="status"></strong>.<p>

<iframeid="widget"width="800"height="400"src="http://localhost:8091/PostMessage/Default.aspx"></< span> iframe>

</body>

</html>

 

代碼片斷二,B.com/b.html

<htmlxmlns="http://www.w3.org/1999/xhtml">

<headrunat="server">

<title>B.com/b.html</title>

<scripttype="text/javascript">

//檢查postMessage 是否能夠用:window.postMessage===undefined

//定義信任的消息源

var trustedOrigin = "http://localhost:8090";

function messageHandler(e) {

if (e.origin === "http://localhost:8090") {

showMsg(e.data);

} else {

// ignore messages from other origins

}

}

 

function sendString(s) {

window.top.postMessage(s, trustedOrigin); //第二個參數是消息傳送的目的地

}

 

function loadDemo() {

addEvent(document.getElementById("actionButton"), "click", function () {

var messageText = document.getElementById("messageText").value;

sendString(messageText);

});

}

 

function showMsg(message) {

document.getElementById("status").innerHTML = message;

}

 

function addEvent(obj, trigger, fun) {

if (obj.addEventListener) obj.addEventListener(trigger, fun, false);

elseif (obj.attachEvent) obj.attachEvent('on' + trigger, fun);

else obj['on' + trigger] = fun;

}

addEvent(window, "load", loadDemo);

addEvent(window, "message", messageHandler);

</script>

</head>

<body>

<h1>B.com/b.html</h1>

<p><b></b>: http://localhost:8091</p>

<p>接收到來自a.com/a.html的消息: <strongid="status"></strong>.<p>

<div>

<inputtype="text"id="messageText"value="msg from b.com/b.html">

<buttonid="actionButton">a.com/a.html發送一個消息</button>

</div>

</body>

</html>

 

代碼的關鍵是message事件是一個擁有data(數據)和origin(來源)屬性的DOM事件。data屬性是發送的實際數據,origin屬性是發送來源。Origin屬性很關鍵,有了這個屬性,接收方能夠輕易的忽略掉來自不可信源的消息,也就能有效避免跨域通訊這個開口給咱們的源安全帶來的隱患。接口很強大,因此代碼很簡單。咱們能夠抓包看一下,這個通訊過程徹底是在瀏覽器端的,沒有產生任何的網絡請求。同時這個接口目前已經獲得了絕大多數瀏覽器的支持,包括IE8及以上版本,參見下面的圖表:

7

可是爲了覆蓋ie6等低版本瀏覽器,咱們完整的方案裏面仍是要包含一下兼容代碼,就是最開始介紹的代理頁面的方法了,但必須是以postMessage爲主,這樣即使最後會有某些瀏覽器由於這種通訊產生一些網絡請求,比例也是很是低的了。

本人的小站「微聚合」也許有更多你感興趣的內容,歡迎交流。

相關文章
相關標籤/搜索