在工作過(guò)程中遇到了跨域問(wèn)題,參考了IBM一篇文章,寫(xiě)了一篇讀后感窟蓝,其實(shí)是按照自己的理解把原文幾乎翻譯了一遍,希望對(duì)大家有所幫助饱普,如果有問(wèn)題歡迎大家指正~原文地址: https://www.ibm.com/developerworks/library/wa-crossdomaincomm/
出于安全考慮运挫,瀏覽器有一些限制状共,在請(qǐng)求url資源的時(shí)候,域名谁帕、端口峡继、應(yīng)用協(xié)議,只要有一個(gè)和當(dāng)前頁(yè)面不同就認(rèn)為不同源匈挖,也就不允許訪問(wèn)碾牌,我們叫它sop(same origin policy)同源策略。栗子:現(xiàn)在有2個(gè)不同源的頁(yè)面A和頁(yè)面B儡循。
A頁(yè)面可以做的事:
1舶吗、從頁(yè)面B獲取css、js择膝、圖片文件
2誓琼、包含一個(gè)資源指向頁(yè)面B的iframe/frame元素
3、通過(guò)HTML元素的src屬性肴捉,向頁(yè)面B傳遞一些信息腹侣,比如ifram或img
A頁(yè)面不能做的事:
1、發(fā)起一個(gè)請(qǐng)求B頁(yè)面的ajax請(qǐng)求
2每庆、獲得或操作指向B頁(yè)面的iframe/frame的內(nèi)容
之所以增加這個(gè)限制筐带,就是防止在不同的網(wǎng)頁(yè)互相交換數(shù)據(jù)時(shí)今穿,保護(hù)用戶不受有害的攻擊缤灵。
有很多解決方案,例如:jsonp證明一個(gè)網(wǎng)頁(yè)可以動(dòng)態(tài)從其他的源加載腳本蓝晒,但是jsonp有2個(gè)主要的限制:1腮出、沒(méi)有錯(cuò)誤處理機(jī)制 2、必須用get方法芝薇,這就有了url長(zhǎng)度限制胚嘲,下面介紹幾個(gè)解決方案,每個(gè)方案各有優(yōu)缺點(diǎn)洛二,需要根據(jù)應(yīng)用場(chǎng)景選擇不同的解決方案馋劈。
Cross-subdomain solution###
原理圖:
頁(yè)面A和頁(yè)面B擁有同一個(gè)父域,可以通過(guò)設(shè)置document的domain屬性來(lái)通信晾嘶,栗子:A的域:www.xyy.com妓雾,B的域:chifan.xyy.com,這2個(gè)源有共同的父域xyy.com,就可以同時(shí)在A和B的html頁(yè)面中這樣設(shè)置:document.domain=xyy.com, 只能設(shè)置成父域垒迂,否則會(huì)報(bào)錯(cuò):Failed to set the 'domain' property on 'Document': 'corp.elong' is not a suffix of ‘127.0.0.1’。同一個(gè)父域下的2個(gè)子域通過(guò)設(shè)置共同的domain屬性可以互相通信机断,這種解決方案適用內(nèi)網(wǎng)應(yīng)用绣夺。
那么現(xiàn)在問(wèn)題來(lái)了欢揖,如果2個(gè)頁(yè)面沒(méi)有同一個(gè)父域怎么辦陶耍?就用下面一個(gè)稍微迂回的方法。
Cross-fragment technique###
原理圖:
在這個(gè)圖中如果A想和iframeB交互物臂,A首先會(huì)創(chuàng)建一個(gè)iframe,這個(gè)frame指向和B有著共同域名的“proxy C”,在C的url中
包含要發(fā)給B的所有參數(shù)产上、數(shù)據(jù)棵磷、frame標(biāo)識(shí)。上代碼:
function sendMsg(msg){
var frame = document.createElement(“iframe”);
var baseProxy = “http://www.otherapp.com/proxy.html”;
var request = {frameName:’otherApp’,data:msg};設(shè)置要交互的frame名稱
frame.src = baseProxy+”#”+encodeURI (dojo.toJson(request));//把要發(fā)送的msg添加到proxy的url里
frame.style.display=”none”;
document.body.appendChild(frame);
}
當(dāng)C加載后晋涣,會(huì)從url獲取A發(fā)來(lái)的數(shù)據(jù)仪媒,并且調(diào)用B的一個(gè)方法谢鹊,因?yàn)锽和C是同一個(gè)域名,所以C可以直接調(diào)用B的方法偎巢。
同理兼耀,B可以用統(tǒng)樣的方法給A返回?cái)?shù)據(jù)。
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部分取出數(shù)據(jù)
var data = obj.data;
//process data
parent.frames[obj.frameName].getData(…);// 調(diào)用frameB的getData方法
}
}
URL.hash(fragment id) solution###
一個(gè)url由幾部分組成窍霞,見(jiàn)圖:
一般的拯坟,改變一個(gè)url會(huì)導(dǎo)致頁(yè)面的刷新,改變hash部分除外冷溃。(hash:我們俗稱的錨點(diǎn)梦裂,#后面跟著一個(gè)字符串,不會(huì)被當(dāng)做參數(shù)解析) 改變url的hash不會(huì)導(dǎo)致刷新頁(yè)面菠净,hash目前被廣泛應(yīng)用到
web 2.0當(dāng)部分刷新頁(yè)面時(shí)標(biāo)志每一步操作。在跨域交互時(shí)hash是一個(gè)很有用的特性毅往,不同源之間的文檔可以設(shè)置其他源
url的hash,盡管它們?cè)讷@取對(duì)方的hash時(shí)有限制攀唯,不同源之間可以通過(guò)hash發(fā)送消息洁桌。
栗子:
原理圖:
用幾段代碼說(shuō)明一下:
從A向B發(fā)送數(shù)據(jù):
function sendMsg(originURL, msg){
var data = {from:originURL, msg:msg};
var src = originURL + “#” + dojo.toJson(data); //把要發(fā)送的消息放到hash
document.getElementById('domainB').src=src;
}
B監(jiān)聽(tīng)從A過(guò)來(lái)的消息:
window.oldHash="";
checkMessage = function(){
var newHash = window.location.hash; //獲取當(dāng)前頁(yè)面url的hash
if(newHash.length > 1){
newHash = newHash.substring(1,newHash.length);
if(newHash != oldHash){ //如果檢測(cè)到和過(guò)去的hash不同另凌,就向A發(fā)送消息
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一樣戒幔,這個(gè)方法也有長(zhǎng)度限制,但是它可以更好的進(jìn)行錯(cuò)誤處理工坊。如果要傳遞一些像?的保留字符敢订,
需要encode一下。
function sendMsg(originURL, msg){
…
var src = originURL + “#” + encodeURI (dojo.toJson(data));
…
}
jsonp為啥有長(zhǎng)度限制楚午?通過(guò)把不同源的url放在<script src=“other.origin.com:1001”></script>標(biāo)簽src的屬性矾柜,如果想要傳遞參數(shù),需要把所有要傳遞的參數(shù)放在get請(qǐng)求的url里把沼,src有長(zhǎng)度限制饮睬,因此傳遞的數(shù)據(jù)也有長(zhǎng)度限制篮奄。jsonp參考:https://web.archive.org/web/20160304044218/http://www.json-p.org/
OpenAjax implementation###
openAjax基于fragment id 和 cross-frame 實(shí)現(xiàn)跨域交互,簡(jiǎn)單來(lái)說(shuō)昼丑,openAjax統(tǒng)一管理不同源之間的數(shù)據(jù)交互夸赫,負(fù)責(zé)管理的模塊暫且叫成“master”,master有一個(gè)message的容器用來(lái)存message,每個(gè)源的iframe有自己的client side,client side有自己的container呼奢,當(dāng)iframe想要發(fā)消息時(shí),是它自己的container代替它向master的container發(fā)送消息握础,其他iframe通過(guò)自己的container監(jiān)聽(tīng)master的消息禀综,工作原理見(jiàn)下圖。
Window.name solution###
window的name屬性特性:當(dāng)頁(yè)面重新加載后name的值不變孤澎,window的name屬性可以被設(shè)置欠窒,利用這個(gè)特性實(shí)現(xiàn)跨域數(shù)據(jù)交互。原理圖:
當(dāng)A想要獲取B的內(nèi)容姐扮,A創(chuàng)建一個(gè)隱形的iframeB,指向B的url,當(dāng)獲取數(shù)據(jù)之后衣吠,在iframeB中把window.name設(shè)置成返回的數(shù)據(jù),這時(shí)候把頁(yè)面重定向到A的域名惊搏,A從window.name即可獲得返回的數(shù)據(jù)忧换。通過(guò)window.name傳遞數(shù)據(jù)的長(zhǎng)度比hash要多的多,大多數(shù)現(xiàn)代瀏覽器支持window.name傳輸16M+的數(shù)據(jù)酪耳。
H5中關(guān)于跨域傳輸?shù)男绿匦?##
window.postMessage(message, targetOrigin)實(shí)現(xiàn)安全跨域交互刹缝。當(dāng)調(diào)用這個(gè)函數(shù)時(shí),會(huì)分發(fā)一個(gè)消息事件言疗,如果window正在監(jiān)聽(tīng)這個(gè)消息事件颂砸,它可以獲得消息內(nèi)容并且知道發(fā)消息的源哪個(gè)死姚,下面是栗子:
http://www.otherapp.com/index.html
function postMessage(msg){
var targetWindow = parent.window;
targetWindow.postMessage(msg,"*”);//觸發(fā)消息事件
}
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); //注冊(cè)監(jiān)聽(tīng)的事件
window.onLoad = function(){
postMessage("already loaded");
}