常用跨域方法实践(二)
本文承接上一篇博文,上一篇博文介绍了跨域的相关概念、测试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>
效果:
结语
本文介绍了常用的五种跨域实现方法,测试源码请戳这!
参考
建议继续学习:
- JSONP与POST方式请求 (阅读:10362)
- JSON和JSONP的区别 (阅读:7693)
- 优雅绝妙的Javascript跨域问题解决方案 (阅读:6744)
- jQuery中getJSON跨域原理详解 (阅读:5623)
- 跨域请求的iframe解决方案(1) (阅读:5396)
- ajax-cross-domain (阅读:4990)
- 利用跨域资源共享(CORS)实现ajax跨域调用 (阅读:4151)
- 研究ext发现ajax跨域实现 (阅读:3744)
- 三谈Iframe自适应高度 (阅读:3642)
- Ajax和WEB服务数据格式:JSON JSONP (阅读:3529)
扫一扫订阅我的微信号:IT技术博客大学习
- 作者:chenjun 来源: 风影博客
- 标签: CORS JSONP 跨域
- 发布时间:2016-02-20 16:56:08
- [44] 界面设计速成
- [39] Oracle MTS模式下 进程地址与会话信
- [38] 视觉调整-设计师 vs. 逻辑
- [35] 如何拿下简短的域名
- [35] 程序员技术练级攻略
- [35] IOS安全–浅谈关于IOS加固的几种方法
- [33] android 开发入门
- [33] 图书馆的世界纪录
- [32] 【社会化设计】自我(self)部分――欢迎区
- [30] 读书笔记-壹百度:百度十年千倍的29条法则