前端跨域問題在大型網站中是比較常見的問題。本文詳細介紹了利用 easyXDM 解決前端跨域的原理細節和使用細節,具體使用時能夠在文中代碼實例的基礎上擴展完成。javascript
因個別網絡運營商存在 HTTP 劫持的狀況,致使網站某些重要的 iframe 彈窗頁面被插入了第三方廣告,內容徹底被遮擋,嚴重影響用戶體驗。公司決定將這些頁面切換爲 HTTPS,切換後發現原來 iframe 浮層自動適應大小的功能失效了,緣由是主頁面是 HTTP 的,子窗口加載後對父頁面浮層大小的操做跨域了,被瀏覽器限制沒法操做。因而就須要跨域解決方案來解決這種狀況。html
介紹一下什麼是跨域問題?網站頁面間發生數據請求和傳輸時,只要兩個網址中的協議名 protocol、主機 host、端口號 port 三個中的任意一個不一樣,就構成了跨域。跨域的頁面默認狀況下不能經過 JavaScript 直接操做對方的頁面對象。前端
各類跨域方案簡單對好比下:java
上述各類跨域方案本文不作展開,有興趣的同窗能夠參考《深刻理解前端跨域方法和原理》( blog.csdn.net/kongjiea/ar… )。git
這裏着重推薦 easyXDM ,由於 easyXDM 集成了現有的多種跨域解決方案,並且很好地實現了跨瀏覽器兼容、多個跨域通訊並行、跨域請求白名單、通訊響應等功能,能完美地解決各類跨域使用的應用場景。github
父頁面 index.html
核心代碼:json
<div id="container"></div>
<div id="output">
<p>藍色區域爲主頁面內容輸出區</p>
</div>
<script src="easyXDM.min.js"></script>
<script> var showMsg = function (message) { document.getElementById('output').innerHTML += "<p>" + message + "</p>"; }; var rpc = new easyXDM.Rpc({ isHost: true, remote: 'http://127.0.0.1/easyXDM/iframe.html', hash: true, protocol: '1', container: document.getElementById('container'), props: { frameBorder: 0, scrolling: 'no', style: {width: '100%', height: '100px'} } }, { local: { echo: function (message) { showMsg(message); } } }); </script>複製代碼
子頁面 iframe.html
核心代碼:windows
<p>實線框爲子頁面區域</p>
<button id="btn" value="">點擊給主頁面發數據</button>
<div id="output"></div>
<script src="easyXDM.min.js"></script>
<script> var showMsg = function (message) { document.getElementById('output').innerHTML += "<p>" + message + "</p>"; }; window.rpc = new easyXDM.Rpc({ isHost: false, //acl: '^(https?:\\/\\/)?([a-zA-Z0-9\\-]+\\.)*baixing.com(\\/.*)?$', protocol: '1' }, { remote: { echo: {} } }); document.getElementById('btn').onclick = function () { rpc.echo('echo from iframe'); }; </script>複製代碼
訪問 http://localhost/easyXDM/index.html
,由於 index.html
和 iframe.html
兩個頁面的 host 不一樣,子頁面操做主頁面內容屬於跨域訪問。有了 easyXDM 做爲通道,這個操做就能夠正常進行了,效果以下圖所示:後端
實際應用場景中,修改調用函數就可讓子頁面對父頁面作任何想作的事情了。跨域
easyXDM 對不一樣的底層通訊方案進行封裝,好比上面實例中使用了 postMessage()
方案來實現跨域雙向通訊。
easyXDM 將方法調用操做進行打包後經過 postMessage()
發送給主頁面,主頁面的 message 處理函數收到數據後交由 easyXDM 進行解析後調起調用函數。代碼調用和數據流以下圖所示:
傳遞的數聽說明:
defaultXXX
: 爲通道標識符,頁面不刷新的狀況下,這個值不變id
: 請求編號,自增,每發送一次請求加1method
: 須要調用的方法名params
: 調用方法的參數,以 JSON 格式表示jsonrpc
: 表示 JSON-RPC 消息版本easyXDM 一樣會調用 postMessage
將方法響應發回給子頁面,子頁面的 message 處理函數收到數據後交由 easyXDM 進行解析,解析後執行對應的響應處理操做。代碼調用和數據流以下圖所示:
傳遞的數聽說明:
defaultXXX
: 爲通道標識符,頁面不刷新的狀況下,這個值不變;與子頁面發送的數據一致id
: 與調用方法時發送的 id 一致result
: 方法響應,以 JSON 格式表示jsonrpc
: 表示 JSON-RPC 消息版本如下依次對主頁面和子頁面的代碼作具體說明。
主頁面調用 easyXDM.Rpc()
的時候會初始化通訊組件,同時會建立 iframe 子頁面;具體參數含義介紹以下:
isHost
: true,表示建立 iframe 子頁面remote
: 建立的 iframe 子頁面的 urlcontainer
: 值爲 DOM 對象,建立出來的 iframe 會被包含在 container 中props
: 屬性中指定的內容會被附加到 iframe 對象上hash
: 爲 true 表明通道相關的 xdm_e / xdm_c / xdm_p 參數會在網址 hash 中記錄,爲 false 時會變成 url 參數;通常狀況下建議設爲 true,由於把跨域相關的前端參數傳遞給後端並非個很好的方式,但能夠解決後面的表單提交後的通道保持問題;因此具體場景具體選擇。經過合理設置以上屬性,就能夠將原來寫死在頁面上的 iframe 改成經過 easyXDM.Rpc()
的方式進行加載,從而實現代碼的靈活嵌入。
上文實例中父頁面 RPC 初始化後的網頁元素以下:
<div id="container">
<iframe name="easyXDM_default5491_provider" id="easyXDM_default5491_provider" frameborder="0" scrolling="no" src="http://127.0.0.1/easyXDM/iframe.html#xdm_e=http%3A%2F%2Flocalhost&xdm_c=default5491&xdm_p=1" style="width: 100%; height: 100%;">
</iframe>
</div>複製代碼
其中 iframe 的 name 和 id 是自動生成的,做用是區分不一樣的 RPC 通道,也就意味着在一個頁面上能夠創建多個跨域調用的通道。中間的 xdm_e / xdm_c / xdm_p 參數是初始化後的通道參數。
另外 local 參數配置定義了子頁面能夠調用的函數方法名和方法實現,方法名、方法參數等均可以任意按需指定。
iframe 中的 RPC 參數的解析以下:
isHost
: false,表明這是客戶端,不建立 iframe 頁面protocol
: 通訊協議,數字,具體含義見如下通訊協議說明部分,可選acl
: 代碼調用方的網址白名單,可選與主頁面的 local 參數相對應,子頁面的 remote 配置定義了全部子頁面須要調用到的主頁面的方法名。只有在 remote 裏定義了,在子頁面上才能經過 RPC 實例調用到。
以上正確配置後,函數跨域調用就和本地調用效果同樣了,具體中間的通訊已經由 easyXDM 來搞定,如同文中的 rpc.echo()
已經能夠直接調用到主頁面定義的 echo
方法。
關於通訊協議,如在代碼配置中未指定則會按如下規則依次匹配使用最前面符合的一個
4
: 當通訊的兩端屬於同一域時,直接通訊1
: 當存在 windows.postMessage
或 document.postMessage
時(IE8+、Firefox 3+、Opera 9+、Chrome 2+、Safari 4+ 支持),使用 postMessage
機制通訊6
: 配置中存在 swf 屬性,而且支持 window.ActiveXObject
時,經過配置的 swf 作通訊5
: Gecko( Firefox 1+ )瀏覽器時,使用 window.frameElement
屬性作通訊2
: 配置中存在 remoteHelper 時,經過配置的 remoteHelper 作通訊0
: 默認,全部瀏覽器都支持;以上規則都不符合時,使用 image 加載機制作通訊index.html
頁面的 echo 函數增長 return 語句返回值:
<script> new easyXDM.Rpc({ // ... }, { local: { echo: function (message) { document.getElementById('output').innerHTML += "<p>" + message + "</p>"; return {'msg': 'echo done from index'}; } }, remote: {} }); </script>複製代碼
iframe.html
調用 RPC 方法時增長回調函數便可:
<script> // ... document.getElementById('btn').onclick = function () { rpc.echo('echo from iframe', function (response) { showMsg(response.msg); }, function (errorObj) { alert('error'); }); }; </script>複製代碼
效果以下圖所示:
在 iframe.html
中 RPC 的 local 中註冊訪問本身頁面內容的方法 pingIframe
:
window.rpc = new easyXDM.Rpc({
// ...
},
{
local: {
pingIframe: function (message) {
showMsg(message);
return {'msg': 'pong from iframe'}
}
},
remote: {
echo: {}
}
});複製代碼
在 index.html
中 RPC 的 remote 中註冊子頁面的 pingIframe
方法聲明,增長一下按鈕調用事件:
<button id="btn" value="">點擊給子頁面發數據</button>
<script> // ... var rpc = new easyXDM.Rpc({ // ... }, { local: { // ... }, remote: { pingIframe: {} } }); document.getElementById('btn').onclick = function () { rpc.pingIframe('ping from index', function(response){ showMsg(response.msg); }, function(errorObj){ alert('error'); }); }; </script>複製代碼
效果以下圖所示:
要作多頁面通訊,只要重複一下相似的相關代碼調用便可。本實例中,複製上面的 iframe.html
爲 iframe2.html
並簡單修改裏面的文字作區分;同時修改 index.html
代碼以下:
<div id="container"></div>
<button id="btn" value="">點擊給子頁面1發數據</button>
<button id="btn2" value="">點擊給子頁面2發數據</button>
<div id="output">
藍色區域爲主頁面內容輸出區
</div>
<script src="easyXDM.min.js"></script>
<script> var showMsg = function (message) { document.getElementById('output').innerHTML += "<p>" + message + "</p>"; }; var generateRpc = function (url) { return new easyXDM.Rpc({ isHost: true, remote: url, hash: true, protocol: '1', container: document.getElementById('container'), props: { frameBorder: 0, scrolling: 'no', style: {width: '100%', height: '100px'} } }, { local: { echo: function (message) { showMsg(message); return {'msg': 'echo done from index'}; } }, remote: { pingIframe: {} } }); }; var bindRpc = function(rpc, btnId) { document.getElementById(btnId).onclick = function () { rpc.pingIframe('ping from index', function (response) { showMsg(response.msg); }, function (errorObj) { alert('error'); }); }; }; var rpc1 = generateRpc('http://127.0.0.1/easyXDM/iframe.html'); bindRpc(rpc1, 'btn'); var rpc2 = generateRpc('http://127.0.0.1/easyXDM/iframe2.html'); bindRpc(rpc2, 'btn2'); </script>複製代碼
效果以下圖所示:
在 hash
設置爲 false
時不作額外處理的狀況下,當提交子頁面裏的 form 或點擊子頁面裏的超連接打開新頁面後,會發現與父窗口的通訊走不通了。究其緣由,是由於切換頁面後,通訊通道相關的 xdm_e / xdm_c / xdm_p 參數丟掉了,致使沒法保持通訊。解決辦法就是,在新打開的頁面網址中將通道參數傳遞過去。爲方便起見,引入 jQuery 庫,代碼以下:
/* 使用方法: * 1. 將如下代碼加入到子頁面中 * 2. 在子頁面的 form 或 a 標籤中增長 easyxdm 類名,將 easyXDM 參數經過網址 * 傳遞給新頁面以保持頁面跳轉後跨域通訊能保持 */
$(document).ready(function () {
$('form.easyxdm').each(function () {
var $form = $(this);
var action = $form.attr('action');
$form.attr('action', action + window.location.hash);
});
$('a.easyxdm').each(function () {
var $link = $(this);
var href = $link.attr('href');
$link.attr('href', href + window.location.hash);
});
});複製代碼
使用 easyXDM 庫過程當中若是遇到一些未知錯誤,能夠經過加載調試庫來作前端調試,步驟以下:
src
目錄複製到本身的代碼目錄下easyXDM.debug.js
本文完整代碼下載:pan.baidu.com/s/1cpRlim
由於 easyXDM 庫自己 README.md 已經好久沒有維護更新,致使一些參數含義沒法找到;文檔對於原理實現未作講解,筆者在使用過程遇到了很多問題,只能經過代碼調試和閱讀代碼的方式深刻了解其實現原理來解決。本文便是筆者使用 easyXDM 的一些總結,供各位看官參考。
做者:南智敏
簡介:百姓網營收技術團隊成員。本文僅爲做者我的觀點,不表明百姓網立場。
題圖做者:Pic2.me
本文在 「百姓網技術團隊」 微信公衆號首發,掃碼當即訂閱: