完美解决前端跨域之 easyXDM 的使用和解析

前端跨域问题在大型网站中是比较常见的问题。本文详细介绍了利用 easyXDM 解决前端跨域的原理细节和使用细节,具体使用时能够在文中代码实例的基础上扩展完成。javascript

0、背景

因个别网络运营商存在 HTTP 劫持的状况,致使网站某些重要的 iframe 弹窗页面被插入了第三方广告,内容彻底被遮挡,严重影响用户体验。公司决定将这些页面切换为 HTTPS,切换后发现原来 iframe 浮层自动适应大小的功能失效了,缘由是主页面是 HTTP 的,子窗口加载后对父页面浮层大小的操做跨域了,被浏览器限制没法操做。因而就须要跨域解决方案来解决这种状况。html

一、跨域问题

介绍一下什么是跨域问题?网站页面间发生数据请求和传输时,只要两个网址中的协议名 protocol、主机 host、端口号 port 三个中的任意一个不一样,就构成了跨域。跨域的页面默认状况下不能经过 JavaScript 直接操做对方的页面对象。前端

各类跨域方案简单对好比下:java

上述各类跨域方案本文不作展开,有兴趣的同窗能够参考《深刻理解前端跨域方法和原理》( blog.csdn.net/kongjiea/ar… )。git

这里着重推荐 easyXDM ,由于 easyXDM 集成了现有的多种跨域解决方案,并且很好地实现了跨浏览器兼容、多个跨域通讯并行、跨域请求白名单、通讯响应等功能,能完美地解决各类跨域使用的应用场景。github

二、easyXDM 使用实例

父页面 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.htmliframe.html 两个页面的 host 不一样,子页面操做主页面内容属于跨域访问。有了 easyXDM 做为通道,这个操做就能够正常进行了,效果以下图所示:后端

实际应用场景中,修改调用函数就可让子页面对父页面作任何想作的事情了。跨域

三、easyXDM 原理解析

3.1 原理说明

easyXDM 对不一样的底层通讯方案进行封装,好比上面实例中使用了 postMessage() 方案来实现跨域双向通讯。

3.1.1 子页面发送数据给主页面

easyXDM 将方法调用操做进行打包后经过 postMessage() 发送给主页面,主页面的 message 处理函数收到数据后交由 easyXDM 进行解析后调起调用函数。代码调用和数据流以下图所示:

easyxdm request
easyxdm request

传递的数听说明:

  • defaultXXX: 为通道标识符,页面不刷新的状况下,这个值不变
  • id: 请求编号,自增,每发送一次请求加1
  • method: 须要调用的方法名
  • params: 调用方法的参数,以 JSON 格式表示
  • jsonrpc: 表示 JSON-RPC 消息版本

3.1.2 主页面方法返回响应数据

easyXDM 一样会调用 postMessage 将方法响应发回给子页面,子页面的 message 处理函数收到数据后交由 easyXDM 进行解析,解析后执行对应的响应处理操做。代码调用和数据流以下图所示:

easyxdm response
easyxdm response

传递的数听说明:

  • defaultXXX: 为通道标识符,页面不刷新的状况下,这个值不变;与子页面发送的数据一致
  • id: 与调用方法时发送的 id 一致
  • result: 方法响应,以 JSON 格式表示
  • jsonrpc: 表示 JSON-RPC 消息版本

如下依次对主页面和子页面的代码作具体说明。

3.2 主页面调用代码解析

主页面调用 easyXDM.Rpc() 的时候会初始化通讯组件,同时会建立 iframe 子页面;具体参数含义介绍以下:

  • isHost: true,表示建立 iframe 子页面
  • remote: 建立的 iframe 子页面的 url
  • container: 值为 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&amp;xdm_c=default5491&amp;xdm_p=1" style="width: 100%; height: 100%;">
    </iframe>
</div>复制代码

其中 iframe 的 name 和 id 是自动生成的,做用是区分不一样的 RPC 通道,也就意味着在一个页面上能够创建多个跨域调用的通道。中间的 xdm_e / xdm_c / xdm_p 参数是初始化后的通道参数。

另外 local 参数配置定义了子页面能够调用的函数方法名和方法实现,方法名、方法参数等均可以任意按需指定。

3.3 子页面调用代码解析

iframe 中的 RPC 参数的解析以下:

  • isHost: false,表明这是客户端,不建立 iframe 页面
  • protocol: 通讯协议,数字,具体含义见如下通讯协议说明部分,可选
  • acl: 代码调用方的网址白名单,可选

与主页面的 local 参数相对应,子页面的 remote 配置定义了全部子页面须要调用到的主页面的方法名。只有在 remote 里定义了,在子页面上才能经过 RPC 实例调用到。

以上正确配置后,函数跨域调用就和本地调用效果同样了,具体中间的通讯已经由 easyXDM 来搞定,如同文中的 rpc.echo() 已经能够直接调用到主页面定义的 echo 方法。

3.4 通讯协议说明

关于通讯协议,如在代码配置中未指定则会按如下规则依次匹配使用最前面符合的一个

  • 4: 当通讯的两端属于同一域时,直接通讯
  • 1: 当存在 windows.postMessagedocument.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 加载机制作通讯

四、更多功能

4.1 增长请求响应处理

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>复制代码

效果以下图所示:

4.2 主页面调用子页面方法

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>复制代码

效果以下图所示:

4.3 主页面与多个页面通讯

要作多页面通讯,只要重复一下相似的相关代码调用便可。本实例中,复制上面的 iframe.htmliframe2.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>复制代码

效果以下图所示:

4.4 iframe 切换页面后保持 RPC 通讯

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 库的调试

使用 easyXDM 库过程当中若是遇到一些未知错误,能够经过加载调试库来作前端调试,步骤以下:

  1. 从 easyXDM GitHub 库 ( github.com/oyvindkinse… ) 拉取完整分支
  2. src 目录复制到本身的代码目录下
  3. 在引入 easyXDM 库的地方改成引入 easyXDM.debug.js
  4. 以后就能够利用 Chrome 浏览器进行 JavaScript 调试了。具体调试方法本文不作展开,有兴趣的同窗能够参考《前端 Chrome 浏览器调试总结》( www.jianshu.com/p/b25c5b88b… ) 的 “Sources 资源页面的断点调试” 部分。

本文完整代码下载:pan.baidu.com/s/1cpRlim

六、尾注

由于 easyXDM 库自己 README.md 已经好久没有维护更新,致使一些参数含义没法找到;文档对于原理实现未作讲解,笔者在使用过程遇到了很多问题,只能经过代码调试和阅读代码的方式深刻了解其实现原理来解决。本文便是笔者使用 easyXDM 的一些总结,供各位看官参考。

七、参考文档:

  1. easyXDM官网 easyxdm.net
  2. easyXDM GitHub库 github.com/oyvindkinse…

做者:南智敏
简介:百姓网营收技术团队成员。

本文仅为做者我的观点,不表明百姓网立场。
题图做者:Pic2.me


本文在 “百姓网技术团队” 微信公众号首发,扫码当即订阅:

相关文章
相关标签/搜索