本文承接 上一篇博文,上一篇博文介绍了跨域的相关概念、测试demo的相关配置和
JSONP
和CORS
两种跨域方式的实现。本文主要介绍document.domain
、URL.hash
、cross-fragment
、window.name
和postMessage
这五种方式的跨域实现。
document.domain
如果 A
源和 B
源具有相同的父域名,通过设置 document.domain
属性,就很容易使其相互通信。在 HTML 规范中document.domain
是一个只读属性,现代浏览器允许将其设置为父域名(不是顶级域名)。例如,一个是 www.myapp.com
的页面,可以设置为 myapp.com
,而另一个来自 sample.myapp.com
的页面也可以设置为 myapp.com
,下图展示了document.domain
的工作原理:
通过将不同子域名的 document.domain
属性设置为相同的父域名,来实现不同子域名之间的跨域通信,这并不属于同源策略限制的范畴。但是,严格来说,子域名跨域的解决方案最适用于内部应用之间的跨域通信。
访问页面:document_domain_test.ejs
<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‘);
}
效果:
URL.hash
一个 URL
由下图所示的几个部分组成:
一般来说, URL
的任何改变都重新会加载一个新的网页,除了 hash
的变化, hash
的任何改变都不会导致页面刷新。 hash
已经被广泛使用在支持局部刷新的 SPA
应用中,用来记录用户的访问路径。在跨域解决方案中, hash
也非常有用,来自不同源的页面可以相互设置对方的 URL
,包括 hash
值,但仍被限制获取对方的 hash
值。文档之间可以通过 hash
来相互通信。如下图所示的例子:
访问页面:url_hash_test.ejs
<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.ejs
<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
就可以像主页面传递消息了,这样实际就可以完成双向的跨域通信了。
效果:
Cross-fragment
由于许多网站的 hash
已经被用于其他用途,对于这样的网站用 hash
跨域将非常复杂(需要从 hash
中合并和分离出消息)。因此我们就有了基于 hash
的一个升级版: 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
方法。
效果:
之前整理过一个利用该方法实现 iframe自适应的博文,有兴趣的可以看一下
window.name
window.name
跨域是一个巧妙的解决方案,我们一般使用 window.name
的情况如下:
- 使用
window.frames[windowName]
得到一个子窗口 - 将其设置为链接元素的
target
属性
加载任何页面 window.name
的值始终保持不变。由于 window.name
这个显著的特点,使其适用于在不同源之间进行跨域通信,但这是个不常用的属性。那么怎么在同源策略下使用呢?下图显示了如何使用 window.name
来跨域通信。
当页面 A
想要从另一个源获取资源或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>
效果:
postMessage
HTML5 规范中的新方法 window.postMessage(message, targetOrigin)
可以用于安全跨域通信。当该方法被调用时,将分发一个消息事件,如果窗口监听了相应的消息,窗口就可以获取到消息和消息来源。如下图所示:
如果 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>
效果:
结语
本文介绍了常用的五种跨域实现方法,测试源码 请戳这!
参考
- Improve cross-domain communication with client-side solutions
- 试试跨域通信 - 利用iframe
- html5 postMessage解决跨域、跨窗口消息传递