本文总结了5种较常见的跨域通信方法,如下:
1)jsonp
2)CORS(Cross OriginResource Sharing,跨源资源共享)
3)主域相同可以设置document.domain
4)利用window.name实现跨域
5)利用window.name实现跨域
jsonp
讲解jsonp之前先看一个例子:假设域A.com上有一个页面a.html,代码如下:
var dosomething= function(data){
alert('我是A.com域上的页面,可以被跨域的remote.js文件调用,远程js带来的数据是:' + data.result);
};
</script>
<script src="B.com/b.js"></script>
而B.com上的b.js文件代码如下:
dosomething({"result":"我是远程js带来的数据"});
运行a.html,显然会弹出弹框,显示接受到了远程js带来的数据。
以上代码在A域上声明了处理数据的函数,在B域上将json数据传入此函数,实现调用。但是,我们如何在B域上知道A域定义的函数名呢,这就需要我们在请求脚本文件时将函数名一同发送给B,告诉B“我想要一段调用XXX函数的js代码,请你返回给我”,B获取此函数名,并将数据传入此函数名中进行调用,而这就是jsonp的原理。为了更通用也灵活的使用此方案,可以采用动态创建script的方式,从而避免手动填写script地址。以下看看a.html如何实现:
var dosomething = function(data){
//处理数据
};
// 提供jsonp服务的url地址(不管是什么类型的地址,最终生成的返回值都是一段javascript代码)
var url = "http://B.com/b.php?callback=dosomething";
// 创建script标签,设置其属性
var script = document.createElement('script');
script.setAttribute('src', url);
// 把script标签加入head,此时调用开始
document.getElementsByTagName('head')[0].appendChild(script);
假设B域的中json数据在b.php中,则b.php的实现大致如下:
<?php
$callback = $GET['callback'];//获得函数名
$data = array('a','b','c');//获得数据
echo $callback.'('.json_encode($data).')';//执行函数,并输出结果
?>
这样,我们就将获得的数据传入了约定的函数中,jsonp的过程也就顺利完成。
优点:1)简单易用,兼容性好,可以在老版浏览器中运行;
2)能够直接访问响应文本,支持在浏览器与服务器之间双向通信。
缺点:1)只支持GET请求,而不支持POST等其他请求;
2)安全性不高,若其他域不安全包含恶意代码,除了放弃jsonp调用,没有办法追究;
3)确定jsonp请求失败较难,虽然html5给script标签新增了onerror事件,但浏览器支持程度还不高。
CORS(Cross OriginResource Sharing,跨源资源共享)
由于同源策略,XHR无法访问其他域的资源。CORS的主要思想就是,使用自定义的HTTP头部告诉服务器,可以接受指定域的访问,也就相当于在约定好的情况下临时接触了特定域的同源策略。
假设A.com域要访问B.com的数据,只需在B.com做如下设置即可
header("Access-Control-Allow-Origin:http://A.com");
表示B.com可以接受来自A.com的访问请求,相当于临时接触了同源限制,接下来的访问操作就和不跨域一样。
优点:支持所有类型的http请求,可以使用普通XHR对象请求数据,比jsonp有更好的错误处理机制;
缺点:浏览器支持度不高。
document.domain+iframe
(此访问只适用于主域相同,而子域不同的场景。比如www.A.com和script.A.com)
此方法只需要将如上所述的两个域中的页面设置相同的document.domain,并在www.A.com/a.html中创建一个iframe,将此iframe的地址设置为script.A.com域地址,以此来操控script.A.com。www.A.com/a.html的代码:
document.domain = 'A.com';
var ifr = document.createElement('iframe');
ifr.src = 'http://script.A.com/b.html';
ifr.style.display = 'none';
document.body.appendChild(ifr);
ifr.onload = function(){
var doc = ifr.contentDocument || ifr.contentWindow.document;
// 在这里操纵b.html
};
script.A.com/b.html的代码
document.domain = 'A.com';
利用window.name实现跨域
window.name有个特征:即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name的,每个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的所有页面中的,并不会因新页面的载入而进行重置。比如:
有一个a.html,代码如下:
window.name='我是a.html的Window.name';
window.location ="b.html";
b.html代码如下:
alert(window.name);
运行a.html,会跳转到b.html,并弹出弹窗显示”我是a.html的Window.name“。也就是只要b.html不对window.name做修改,其值都是a.html中设置的window.name值。
仔细回顾一下以上过程,我们会发现,可以利用这个window.name来传输我们需要在不同域中传输的数据。比如A.com/a.html要访问B.com/b.html数据,那么可以在B.com/b.html将数据放在window.name中:
window.name='我是要传输的数据';
但在A.com/a.html,显然不能通过window.location来跳转到B.com/b.html,我们希望可以不用跳转就可以获得数据。此时我们需要借助一个隐藏的媒人iframe,利用iframe去获取B.com/b.html的数据,然后A.com/a.html再去获取iframe得到的数据。
<iframe id="proxy" src="B.com/b.html" style="display: none" onload =loadfn></iframe>
<script>
var state = 0;
var iframe = document.getElementById('proxy');
loadfn = function() {
if (state === 1) {
var data = iframe.contentWindow.name; // 读取数据
alert(data); //弹出'我是要传输的数据'
} else if (state === 0) {
state = 1;
iframe.src = "http://A.com/proxy.html";//设置的代理文件,只要与A.com同域即可
}
};
</script>
上述过程首先将iframe的src设置为B.com/b.html,这样就可以获取B.com/b.html的window.name,也就是需要的数据。然后,如果A.com/a.html想要获得此数据,必须是的iframe与A.com同域,因此采用一个与A.com同域的代理页面A.com/proxy.html去获得此时iframe的contentWindow.name值,也就是之前获得了之后一直没有改变的window.name值。
HTML5的window.postMessage实现跨域
window.postMessage(message,targetOrigin) 方法是html5新引进的特性,可以使用它来向其它的window对象发送消息,无论这个window对象是属于同源或不同源。
window.postMessage(message,targetOrigin) 中的window指的接受数据的window对象,一般是页面中iframe的contentWindow属性,第一个参数message为要发送的消息,类型只能为字符串;第二个参数targetOrigin表示接收数据的那个window对象所在的域。
比如A.com/a.html要访问B.com/b.html的数据,则由B.com/b.html向A.com/a.html发送数据,B.com/b.html的实现如下:
<iframe id="ifr" src="A.com/a.html"></iframe>
<script type="text/javascript">
window.onload = function() {
var ifr = document.getElementById('ifr');
var targetOrigin = 'http://A.com/a.html';
ifr.contentWindow.postMessage('我是数据', targetOrigin);
};
</script>
在A.com/a.html中监听message事件,从而获得发送过来的数据:
window.addEventListener('message', function(event){
// 通过origin属性判断消息来源地址
if (event.origin == 'http://A.com/a.html ') {
alert(event.data); // 弹出"我是数据"
}
}, false);