完美解决前端跨域之 easyXDM 的使用和解析
前端跨域问题在大型网站中是比较常见的问题。本文详细介绍了利用 easyXDM 解决前端跨域的原理细节和使用细节,具体使用时可以在文中代码实例的基础上扩展完成。
0、背景
因个别网络运营商存在 HTTP 劫持的情况,导致网站某些重要的 iframe 弹窗页面被插入了第三方广告,内容完全被遮挡,严重影响用户体验。公司决定将这些页面切换为 HTTPS,切换后发现原来 iframe 浮层自动适应大小的功能失效了,原因是主页面是 HTTP 的,子窗口加载后对父页面浮层大小的操作跨域了,被浏览器限制无法操作。于是就需要跨域解决方案来解决这种情况。
1、跨域问题
介绍一下什么是跨域问题?网站页面间发生数据请求和传输时,只要两个网址中的协议名 protocol、主机 host、端口号 port 三个中的任意一个不同,就构成了跨域。跨域的页面默认情况下不能通过 JavaScript 直接操作对方的页面对象。
各种跨域方案简单对比如下:

上述各种跨域方案本文不做展开,有兴趣的同学可以参考《深入理解前端跨域方法和原理》( http://blog.csdn.net/kongjiea/article/details/44201021 )。
这里着重推荐 easyXDM ,因为 easyXDM 集成了现有的多种跨域解决方案,而且很好地实现了跨浏览器兼容、多个跨域通信并行、跨域请求白名单、通信响应等功能,能完美地解决各种跨域使用的应用场景。
2、easyXDM 使用实例
父页面 index.html
核心代码:
<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
核心代码:
<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 作为通道,这个操作就可以正常进行了,效果如下图所示:

实际应用场景中,修改调用函数就可以让子页面对父页面做任何想做的事情了。
3、easyXDM 原理解析
3.1 原理说明
easyXDM 对不同的底层通信方案进行封装,比如上面实例中使用了 postMessage()
方案来实现跨域双向通信。
3.1.1 子页面发送数据给主页面
easyXDM 将方法调用操作进行打包后通过 postMessage()
发送给主页面,主页面的 message 处理函数收到数据后交由 easyXDM 进行解析后调起调用函数。代码调用和数据流如下图所示:

传递的数据说明:
defaultXXX
: 为通道标识符,页面不刷新的情况下,这个值不变id
: 请求编号,自增,每发送一次请求加1method
: 需要调用的方法名params
: 调用方法的参数,以 JSON 格式表示jsonrpc
: 表示 JSON-RPC 消息版本
3.1.2 主页面方法返回响应数据
easyXDM 同样会调用 postMessage
将方法响应发回给子页面,子页面的 message 处理函数收到数据后交由 easyXDM 进行解析,解析后执行对应的响应处理操作。代码调用和数据流如下图所示:

传递的数据说明:
defaultXXX
: 为通道标识符,页面不刷新的情况下,这个值不变;与子页面发送的数据一致id
: 与调用方法时发送的 id 一致result
: 方法响应,以 JSON 格式表示jsonrpc
: 表示 JSON-RPC 消息版本
以下依次对主页面和子页面的代码做具体说明。
3.2 主页面调用代码解析
主页面调用 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 参数配置定义了子页面可以调用的函数方法名和方法实现,方法名、方法参数等都可以任意按需指定。
3.3 子页面调用代码解析
iframe 中的 RPC 参数的解析如下:
isHost
: false,代表这是客户端,不创建 iframe 页面protocol
: 通信协议,数字,具体含义见以下通信协议说明部分,可选acl
: 代码调用方的网址白名单,可选
与主页面的 local 参数相对应,子页面的 remote 配置定义了所有子页面需要调用到的主页面的方法名。只有在 remote 里定义了,在子页面上才能通过 RPC 实例调用到。
以上正确配置后,函数跨域调用就和本地调用效果一样了,具体中间的通信已经由 easyXDM 来搞定,如同文中的 rpc.echo()
已经可以直接调用到主页面定义的 echo
方法。
3.4 通信协议说明
关于通信协议,如在代码配置中未指定则会按以下规则依次匹配使用最前面符合的一个
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 加载机制做通信
4、更多功能
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.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>
效果如下图所示:

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);
});
});
5、easyXDM 库的调试
使用 easyXDM 库过程中如果遇到一些未知错误,可以通过加载调试库来做前端调试,步骤如下:
- 从 easyXDM GitHub 库 ( https://github.com/oyvindkinsey/easyXDM ) 拉取完整分支
- 将
src
目录复制到自己的代码目录下 - 在引入 easyXDM 库的地方改为引入
easyXDM.debug.js
- 之后就可以利用 Chrome 浏览器进行 JavaScript 调试了。具体调试方法本文不做展开,有兴趣的同学可以参考《前端 Chrome 浏览器调试总结》( http://www.jianshu.com/p/b25c5b88baf5 ) 的 “Sources 资源页面的断点调试” 部分。
本文完整代码下载:https://pan.baidu.com/s/1cpRlim
6、尾注
因为 easyXDM 库本身 README.md 已经很久没有维护更新,导致一些参数含义无法找到;文档对于原理实现未做讲解,笔者在使用过程遇到了不少问题,只能通过代码调试和阅读代码的方式深入了解其实现原理来解决。本文即是笔者使用 easyXDM 的一些总结,供各位看官参考。
7、参考文档:
- easyXDM官网 http://easyxdm.net
- easyXDM GitHub库 https://github.com/oyvindkinsey/easyXDM
作者:南智敏
简介:百姓网营收技术团队成员。本文仅为作者个人观点,不代表百姓网立场。
题图作者:Pic2.me
本文在 “百姓网技术团队” 微信公众号首发,扫码立即订阅:
完美解决前端跨域之 easyXDM 的使用和解析的更多相关文章
- JAVA解决前端跨域问题。
什么是跨域? 通俗来说,跨域按照我自己的想法来理解,是不同的域名之间的访问,就是跨域.不同浏览器,在对js文件进行解析是不同的,浏览器会默认阻止,所以 现在我来说下用java代码解决前端跨域问题. 用 ...
- 用nginx的反向代理机制解决前端跨域问题在nginx上部署web静态页面
用nginx的反向代理机制解决前端跨域问题在nginx上部署web静态页面 1.什么是跨域以及产生原因 跨域是指a页面想获取b页面资源,如果a.b页面的协议.域名.端口.子域名不同,或是a页面为ip地 ...
- 项目部署问题:xftp无法连接服务器、Nginx403 Forbidden解决、nginx反向代理解决前端跨域问题
一.xftp无法连接服务器 在xftp中配置正确的ip,用户名和密码后,居然无法连接 解决方案:将协议里面的FTP换成SFTP,注意换成SFTP后端口就默认换成22,要还是原来的21就还是连不上的哈 ...
- 用nginx的反向代理机制解决前端跨域问题
什么是跨域以及产生原因 跨域是指a页面想获取b页面资源,如果a.b页面的协议.域名.端口.子域名不同,或是a页面为ip地址,b页面为域名地址,所进行的访问行动都是跨域的,而浏览器为了安全问题一般都限制 ...
- 如何用Nginx解决前端跨域问题?
前言 在开发静态页面时,类似Vue的应用,我们常会调用一些接口,这些接口极可能是跨域,然后浏览器就会报cross-origin问题不给调. 最简单的解决方法,就是把浏览器设为忽略安全问题,设置--di ...
- 【Nginx】在Windows下使用Nginx解决前端跨域问题
提出问题:因为一些历史原因,后台代码不能动.请求别人的接口拿数据显示在前端,怎么办呢? 分析问题:通过ajax请求. 解决问题:因为浏览器的同源策略,所以需要解决跨域问题.(同源策略:请求的url地址 ...
- nginx反向代理-解决前端跨域问题
1.定义 跨域是指a页面想获取b页面资源,如果a.b页面的协议.域名.端口.子域名不同,所进行的访问行动都是跨域的,而浏览器为了安全问题一般都限制了跨域访问,也就是不允许跨域请求资源.注意:跨域限制访 ...
- 利用nginx做反向代理解决前端跨域问题
最近朋友再群里提了一个问题,他们公司给他提供了一个获取数据的接口,在浏览器访问这个接口能获取到json数据,但是放在项目里使用ajax就产生了跨域问题,一般这个需要提供接口的后台方面需要做跨域处理,但 ...
- node+express解决前端跨域问题
var express = require('express') , app = express(); //解决跨域 app.all('*',function (req, res, next) { r ...
随机推荐
- ASP.Net Core下Authorization的几种方式 - 简书
原文:ASP.Net Core下Authorization的几种方式 - 简书 ASP.Net Core下Authorization的几种方式 Authorization其目标就是验证Http请求能否 ...
- ado.net SqlHelp类
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...
- 102、如何滚动更新 Service (Swarm09)
参考https://www.cnblogs.com/CloudMan6/p/7988455.html 在前面的实验中,我们部署了多个副本的服务,本节将讨论如何滚动更新每一个副本. 滚动更新降低 ...
- 多Y轴示例
//多Y轴示例 <template> <div id="main" :style="{width:'1000px',height:'500px' }&q ...
- Hyperledger Fabric(1)基础架构
前言 在区块链的家谱里,第一代区块链系统是以比特币为代表的公链,主要实现的是数字货币的功能:第二代区块链系统是以以太坊平台为代表的公链,创造性的实现了智能合约.而第三代区块链系统,则是HyperLed ...
- ERROR qos-server can not bind localhost:22222
dubboe版本2.7.1 spring cloud alibaba最新官网examples 根据readme中说明文档依次启动 1.nacos,默认用户名密码nacos/nacos 2.启动spri ...
- ThinkPHP依赖注入
D:\wamp64\www\thinkphp5.1\tp5.1\application\index\controller\Demo1.php文件 <?php namespace app\inde ...
- 韦东山嵌入式Linux学习笔记05--存储管理器
SDRAM: 原理图如下: jz2440 v3开发板上面用的内存芯片为钰创科技公司生产的EM63A165TS,一片内存大小为32MB大小,一共有两块,共64MB的大小. SDRAM接 ...
- idea集成Jrebel热部署Jrebel 永久免费激活
安装好idea和Jrebel后,按图示方法打开激活页面 选择License server方式 Url:输入 http://139.199.89.239:1008/88414687-3b91-4286- ...
- 孕期出血是否先兆流产——B超看婴儿是否在子宫内+hcg值是否过低孕激素不足
转自:http://blog.sina.com.cn/s/blog_4a869c130102e7nu.html 很多人都经历过孕早期阴道出血,但结局大不一样. 人类受孕后,从一个单细胞逐渐发育成为一个 ...