前端跨域问题在大型网站中是比较常见的问题。本文详细介绍了利用 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
本文在 “百姓网技术团队” 微信公众号首发,扫码当即订阅: