經常使用跨域方法實踐(二)

本文承接 上一篇 博文,上一篇博文介紹了跨域的相關概念、測試demo的相關配置和 JSONP 和 CORS 兩種跨域方式的實現。本文主要介紹 document.domain 、 URL.hash 、 cross-fragment 、 window.name 和 postMessage 這五種方式的跨域實現。html

document.domain

若是 源和 源具備相同的父域名,經過設置 document.domain 屬性,就很容易使其相互通訊。在 HTML 規範中document.domain 是一個只讀屬性,現代瀏覽器容許將其設置爲父域名(不是頂級域名)。例如,一個是 www.myapp.com 的頁面,能夠設置爲 myapp.com ,而另外一個來自 sample.myapp.com 的頁面也能夠設置爲 myapp.com ,下圖展現了document.domain 的工做原理:html5

document-domain

經過將不一樣子域名的 document.domain 屬性設置爲相同的父域名,來實現不一樣子域名之間的跨域通訊,這並不屬於同源策略限制的範疇。可是,嚴格來講,子域名跨域的解決方案最適用於內部應用之間的跨域通訊。git

訪問頁面:document_domain_test.ejsgithub

 <button>測試</button> <div></div> <iframe id="ifr" src="http://sample.myapp.com/document_iframe"></iframe> <script> document.domain = "myapp.com"; function sayHello(str) { document.querySelector('div').innerHTML = str; } document.querySelector('button').onclick = function() { document.querySelector('#ifr').contentWindow.sayHello('Hello son'); } </script>

跨域iframe:document_iframe.ejs跨域

 document.domain = 'myapp.com'; function sayHello(str) { document.querySelector('div').innerHTML = str; parent.sayHello('Hello father'); }

效果:瀏覽器

document-domain-show

URL.hash

一個 URL 由下圖所示的幾個部分組成:安全

url

通常來講, URL 的任何改變都從新會加載一個新的網頁,除了 hash 的變化, hash 的任何改變都不會致使頁面刷新。 hash 已經被普遍使用在支持局部刷新的 SPA 應用中,用來記錄用戶的訪問路徑。在跨域解決方案中, hash 也很是有用,來自不一樣源的頁面能夠相互設置對方的 URL ,包括 hash 值,但仍被限制獲取對方的 hash 值。文檔之間能夠經過 hash 來相互通訊。以下圖所示的例子:app

fragment

訪問頁面:url_hash_test.ejsdom

 <button>發送消息</button> <iframe id="ifr" src="http://www.otherapp.com/hash_iframe"></iframe> <script> var target = "http://www.otherapp.com/hash_iframe"; function sendMsg(msg) { var data = {msg: msg}; var src = target + "#" + JSON.stringify(data); document.getElementById('ifr').src = src; } document.querySelector('button').onclick = function() { sendMsg("Time: " + (new Date())); } </script>

跨域iframe:hash_iframe.ejside

 <div></div> <script> var oldHash = ""; var target = "http://www.myapp.com/url_hash_test"; var checkMessage = function() { var newHash = window.location.hash; if (newHash.length > 1) { newHash = newHash.slice(1, newHash.length); if (newHash !== oldHash) { oldHash = newHash; var msgs = JSON.parse(newHash); var msg = msgs.msg; sendMessage("Hello-father"); document.querySelector('div').innerHTML = msg; } } } setInterval(checkMessage, 1000); function sendMessage(msg) { var hash = "msg=" + msg; parent.location.href = target + "#" + hash; } </script>

主頁面經過改變 iframe 的 hash 值向 iframe 傳遞參數, iframe 不斷獲取本身的 hash 值,一旦發生變化就當即顯示主頁面傳來的消息,而且經過設置主頁面的 hash 就能夠像主頁面傳遞消息了,這樣實際就能夠完成雙向的跨域通訊了。

效果:

url-hash

Cross-fragment

因爲許多網站的 hash 已經被用於其餘用途,對於這樣的網站用 hash 跨域將很是複雜(須要從 hash 中合併和分離出消息)。所以咱們就有了基於 hash 的一個升級版: cross-fragment ,其原理以下圖所示:

cross-fragment

訪問頁面:cross_fragment_test.ejs

 <button>發送消息</button> <div></div> <iframe id="otherapp" name="otherapp" src="http://www.otherapp.com/fragment_iframe"></iframe> <script> function sendMsg(msg) { var frame = document.createElement("iframe"); var baseProxy = "http://www.otherapp.com/fragment_req_proxy"; var request = {frameName: "otherapp", data: msg}; frame.src = baseProxy + "#" + encodeURI(JSON.stringify(request)); frame.style.display = "none"; document.body.appendChild(frame); } // 響應處理函數 function getResponse(data) { document.querySelector('div').innerHTML = "收到結果:" + data; } document.querySelector('button').onclick = function() { // 發送一個隨機數 sendMsg(Math.random()); } </script>

請求代理頁面:fragment_req_proxy.ejs

 <script> var hash = window.location.hash; if (hash && hash.length > 1) { var request = hash.slice(1, hash.length); var obj = JSON.parse(decodeURI(request)); var data = obj.data; //目標頁面的getData方法 parent.frames[obj.frameName].getData(data); } </script>

跨域iframe:fragment_iframe.ejs

 <div></div> <script> function getData(data) { document.querySelector('div').innerHTML = "收到的消息:" + data; var frame = document.createElement("frame"); var baseProxy = "http://www.myapp.com/fragment_res_proxy"; // 將接收的數據擴大一百倍 var request = {data: data * 100}; frame.src = baseProxy + "#" + encodeURI(JSON.stringify(request)); frame.style.display = "none"; document.body.appendChild(frame); } </script>

響應代理頁面:fragment_res_proxy.ejs

 <script> var hash = window.location.hash; if (hash && hash.length > 1) { var request = hash.slice(1, hash.length); var obj = JSON.parse(decodeURI(request)); var data = obj.data; //主頁面的getResponse方法 parent.parent.getResponse(data); } </script>

這個例子中先在主頁面(來自myapp)放置一個otherapp下的 iframe (目標頁面),點擊按鈕後就在主頁面中構造一個隱藏的iframe (和目標頁面同域,來自otherapp,請求代理頁面),經過這個請求代理頁面調用目標頁面的 getData 方法,這個方法接收傳來的數據,處理完成後,構造一個隱藏的 iframe (和主頁面同域,來自myapp,響應代理頁面),經過響應代理頁面調用主頁面中的 getResponse 方法。

效果:

cross-fragment-show

以前整理過一個利用該方法實現 iframe自適應 的博文,有興趣的能夠看一下

window.name

window.name 跨域是一個巧妙的解決方案,咱們通常使用 window.name 的狀況以下:

  • 使用 window.frames[windowName] 獲得一個子窗口
  • 將其設置爲連接元素的 target 屬性

加載任何頁面 window.name 的值始終保持不變。因爲 window.name 這個顯著的特色,使其適用於在不一樣源之間進行跨域通訊,但這是個不經常使用的屬性。那麼怎麼在同源策略下使用呢?下圖顯示瞭如何使用 window.name 來跨域通訊。

window-name

當頁面 想要從另外一個源獲取資源或Web服務,首先在本身的頁面上建立一個隱藏的 iframe B,將 B 指向外部資源或服務,B 加載完成以後,將把響應的數據附加到 window.name 上。因爲如今 A 和 B 還不一樣源,A 依舊不能獲取到 B 的 name 屬性。當 B 獲取到數據以後,再將頁面導航到任何一個與 A 同源的頁面,這時 A 就能夠直接獲取到 B 的 name 屬性值。當 A 獲取到數據以後,就能夠隨時刪掉 B。

訪問頁面:window_name_test.ejs

 <button>發送消息</button> <div class="req"></div> <div class="res"></div> <script> function sendMsg(msg) { var state = 0, data; var frame = document.createElement("frame"); var baseProxy = "http://www.otherapp.com/window_iframe"; var request = {data: msg}; frame.src = baseProxy + "#" + encodeURI(JSON.stringify(request)); frame.style.display = "none"; frame.onload = function() { if (state === 1) { data = frame.contentWindow.name; document.querySelector('.res').innerHTML = "得到響應:" + data; // 刪除iframe frame.contentWindow.document.write(''); frame.contentWindow.close(); document.body.removeChild(frame); } else { state = 1; frame.src = "http://www.myapp.com/window_iframe"; } }; document.body.appendChild(frame); } document.querySelector('button').onclick = function() { var val = Math.random(); sendMsg(val); document.querySelector('.req').innerHTML = "請求數據:" + val; } </script>

跨域iframe:window_iframe.ejs

 <script> var hash = window.location.hash; if (hash && hash.length > 1) { var request = hash.slice(1, hash.length); var obj = JSON.parse(decodeURI(request)); var data = obj.data; window.name = data*100; } </script>

效果: window-name-show

postMessage

HTML5 規範中的新方法 window.postMessage(message, targetOrigin) 能夠用於安全跨域通訊。當該方法被調用時,將分發一個消息事件,若是窗口監聽了相應的消息,窗口就能夠獲取到消息和消息來源。以下圖所示:

post-message

若是 iframe 想要通知不一樣源的父窗口它已經加載完成,可使用 window.postMessage 來發送消息。同時,它也將監聽回饋消息。

訪問頁面:postMessage_test.ejs

 <div></div> <iframe name="otherApp" id="otherApp" src="http://www.otherapp.com/postMessage_iframe"></iframe> <script> function handleReceive(event) { if (event.origin != "http://www.otherapp.com") return; // 處理數據 var data = JSON.parse(event.data); document.querySelector('div').innerHTML = "來自iframe的顏色:" + data.color; var otherAppFrame = window.frames["otherApp"]; otherAppFrame.postMessage("ok", "http://www.otherapp.com"); } window.addEventListener("message", handleReceive, false); </script>

跨域iframe:postMessage_iframe.ejs

 <div></div> <script> function postMessage(msg) { var targetWindow = parent.window; targetWindow.postMessage(msg, "*"); } function handleReceive(msg) { if (msg.data === "ok") { document.querySelector('div').innerHTML = "消息發送成功"; } else { postMessage(JSON.stringify({color: 'red'})); } } window.addEventListener("message", handleReceive, false); window.onload = function() { postMessage(JSON.stringify({color: 'red'})); } </script>

效果:

postMessage-show

結語

本文介紹了經常使用的五種跨域實現方法,測試源碼 請戳這 

參考

相關文章
相關標籤/搜索