在工作过程中遇到了跨域问题,参考了IBM一篇文章,写了一篇读后感,其实是按照自己的理解把原文几乎翻译了一遍,希望对大家有所帮助,如果有问题欢迎大家指正~原文地址: https://www.ibm.com/developerworks/library/wa-crossdomaincomm/
出于安全考虑,浏览器有一些限制,在请求url资源的时候,域名、端口、应用协议,只要有一个和当前页面不同就认为不同源,也就不允许访问,我们叫它sop(same origin policy)同源策略。栗子:现在有2个不同源的页面A和页面B。
A页面可以做的事:
1、从页面B获取css、js、图片文件
2、包含一个资源指向页面B的iframe/frame元素
3、通过HTML元素的src属性,向页面B传递一些信息,比如ifram或img
A页面不能做的事:
1、发起一个请求B页面的ajax请求
2、获得或操作指向B页面的iframe/frame的内容
之所以增加这个限制,就是防止在不同的网页互相交换数据时,保护用户不受有害的攻击。
有很多解决方案,例如:jsonp证明一个网页可以动态从其他的源加载脚本,但是jsonp有2个主要的限制:1、没有错误处理机制 2、必须用get方法,这就有了url长度限制,下面介绍几个解决方案,每个方案各有优缺点,需要根据应用场景选择不同的解决方案。
Cross-subdomain solution###
原理图:
页面A和页面B拥有同一个父域,可以通过设置document的domain属性来通信,栗子:A的域: www.xyy.com,B的域: chifan.xyy.com,这2个源有共同的父域 xyy.com,就可以同时在A和B的html页面中这样设置:document.domain= xyy.com, 只能设置成父域,否则会报错:Failed to set the 'domain' property on 'Document': 'corp.elong' is not a suffix of ‘127.0.0.1’。同一个父域下的2个子域通过设置共同的domain属性可以互相通信,这种解决方案适用内网应用。
那么现在问题来了,如果2个页面没有同一个父域怎么办?就用下面一个稍微迂回的方法。
Cross-fragment technique###
原理图:
在这个图中如果A想和iframeB交互,A首先会创建一个iframe,这个frame指向和B有着共同域名的“proxy C”,在C的url中
包含要发给B的所有参数、数据、frame标识。上代码:
function sendMsg(msg){
var frame = document.createElement(“iframe”);
var baseProxy = “http://www.otherapp.com/proxy.html”;
var request = {frameName:’otherApp’,data:msg};设置要交互的frame名称
frame.src = baseProxy+”#”+encodeURI (dojo.toJson(request));//把要发送的msg添加到proxy的url里
frame.style.display=”none”;
document.body.appendChild(frame);
}
当C加载后,会从url获取A发来的数据,并且调用B的一个方法,因为B和C是同一个域名,所以C可以直接调用B的方法。
同理,B可以用统样的方法给A返回数据。
window.onLoad = function(){
var hash = window.location.hash;
if(hash && hash.length>1){
var request = hash.substring(1,hash.length);
var obj = dojo.fromJson(decodeURI (request));//从url的hash部分取出数据
var data = obj.data;
//process data
parent.frames[obj.frameName].getData(…);// 调用frameB的getData方法
}
}
URL.hash(fragment id) solution###
一个url由几部分组成,见图:
一般的,改变一个url会导致页面的刷新,改变hash部分除外。(hash:我们俗称的锚点,#后面跟着一个字符串,不会被当做参数解析) 改变url的hash不会导致刷新页面,hash目前被广泛应用到
web 2.0当部分刷新页面时标志每一步操作。在跨域交互时hash是一个很有用的特性,不同源之间的文档可以设置其他源
url的hash,尽管它们在获取对方的hash时有限制,不同源之间可以通过hash发送消息。
栗子:
原理图:
用几段代码说明一下:
从A向B发送数据:
function sendMsg(originURL, msg){
var data = {from:originURL, msg:msg};
var src = originURL + “#” + dojo.toJson(data); //把要发送的消息放到hash
document.getElementById('domainB').src=src;
}
B监听从A过来的消息:
window.oldHash="";
checkMessage = function(){
var newHash = window.location.hash; //获取当前页面url的hash
if(newHash.length > 1){
newHash = newHash.substring(1,newHash.length);
if(newHash != oldHash){ //如果检测到和过去的hash不同,就向A发送消息
oldHash = newHash;
var msgs = dojo.fromJson(newHash);
var origin = msgs.from;
var msg = msgs.msg;
sendMessage(origin, "Hello document A");
}
}
}
window.setInterval(checkMessage, 1000); //每隔1秒检查一次hash
sendMessage = function(target, msg){
var hash = "msg="+ msg;
parent.location.href= target + “#” + hash;
}
就像jsonp一样,这个方法也有长度限制,但是它可以更好的进行错误处理。如果要传递一些像?的保留字符,
需要encode一下。
function sendMsg(originURL, msg){
…
var src = originURL + “#” + encodeURI (dojo.toJson(data));
…
}
jsonp为啥有长度限制?通过把不同源的url放在标签src的属性,如果想要传递参数,需要把所有要传递的参数放在get请求的url里,src有长度限制,因此传递的数据也有长度限制。jsonp参考:https://web.archive.org/web/20160304044218/http://www.json-p.org/
OpenAjax implementation###
openAjax基于fragment id 和 cross-frame 实现跨域交互,简单来说,openAjax统一管理不同源之间的数据交互,负责管理的模块暂且叫成“master”,master有一个message的容器用来存message,每个源的iframe有自己的client side,client side有自己的container,当iframe想要发消息时,是它自己的container代替它向master的container发送消息,其他iframe通过自己的container监听master的消息,工作原理见下图。
Window.name solution###
window的name属性特性:当页面重新加载后name的值不变,window的name属性可以被设置,利用这个特性实现跨域数据交互。原理图:
当A想要获取B的内容,A创建一个隐形的iframeB,指向B的url,当获取数据之后,在iframeB中把window.name设置成返回的数据,这时候把页面重定向到A的域名,A从window.name即可获得返回的数据。通过window.name传递数据的长度比hash要多的多,大多数现代浏览器支持window.name传输16M+的数据。
H5中关于跨域传输的新特性###
window.postMessage(message, targetOrigin)实现安全跨域交互。当调用这个函数时,会分发一个消息事件,如果window正在监听这个消息事件,它可以获得消息内容并且知道发消息的源哪个,下面是栗子:
http://www.otherapp.com/index.html
function postMessage(msg){
var targetWindow = parent.window;
targetWindow.postMessage(msg,"*”);//触发消息事件
}
function handleReceive(msg){
var object = dojo.fromJson(msg);
if(object.status == “ok”){
//continue to do other things
……
}else{
//retry sending msg
……
}
}
window.addEventListener("message", handleReceive, false); //注册监听的事件
window.onLoad = function(){
postMessage("already loaded");
}