一、代理跨域
場(chǎng)景1:你的項(xiàng)目myweb矾柜,myweb的前端有一個(gè)接口是去訪問一個(gè)非myweb的服務(wù)器窄刘。非myweb服務(wù)器是第三方服務(wù)器窥妇,你不能去對(duì)第三方服務(wù)器做改動(dòng)。
場(chǎng)景2:你的項(xiàng)目是個(gè)微服務(wù)架構(gòu)的娩践。那你的前端頁面可能就需要去很多個(gè)服務(wù)器上訪問數(shù)據(jù)活翩。
原理解析:
跨域請(qǐng)求報(bào)錯(cuò)歸根結(jié)底是瀏覽器禁止使用XHR對(duì)象向不同源的服務(wù)器地址發(fā)起HTTP請(qǐng)求。如果是服務(wù)器跨域向多個(gè)不同的服務(wù)器發(fā)送請(qǐng)求就不會(huì)有跨域問題存在翻伺。因此材泄,我們可以讓瀏覽器只向一個(gè)服務(wù)器方式請(qǐng)求,讓這個(gè)服務(wù)器代替瀏覽器去不同的服務(wù)器上請(qǐng)求資源再返回給瀏覽器吨岭。這個(gè)服務(wù)器就是代理服務(wù)器了拉宗。
下面推薦一個(gè)常用代理服務(wù)器nginx。
什么是nginx?
Nginx (engine x) 是一款輕量級(jí)的Web 服務(wù)器 辣辫、反向代理服務(wù)器及電子郵件(IMAP/POP3)代理服務(wù)器旦事。
- 把ui所在的服務(wù)器和跨域服務(wù)器都用nginx代理轉(zhuǎn)發(fā),瀏覽器訪問nginx急灭,nginx到ui服務(wù)獲取ui姐浮,再把ui下載到瀏覽器,瀏覽器發(fā)起ui中的URL葬馋,該URL為Nginx封裝后的跨域服務(wù)器的URL或ui服務(wù)器的URL卖鲤,該URL到達(dá)Nginx之后,會(huì)被轉(zhuǎn)發(fā)到跨域服務(wù)器或ui服務(wù)器点楼,請(qǐng)求處理完畢后扫尖,會(huì)通過Nginx中轉(zhuǎn)返回給瀏覽器。暴露出來的或者瀏覽器所發(fā)起的url都是nginx的url掠廓,nginx去跨域服務(wù)器和ui服務(wù)器獲取響應(yīng)换怖,返給瀏覽器,這樣就沒有跨域問題了蟀瞧。
二沉颂、CORS
場(chǎng)景:前后端分離的開發(fā)模式下,在本地進(jìn)行接口聯(lián)調(diào)時(shí):也許在你的項(xiàng)目里悦污,你想嘗試前后端分離的開發(fā)模式铸屉。
你在本地開發(fā)時(shí),mock了一些假數(shù)據(jù)來幫助自己本地開發(fā)切端。而有一天彻坛,你希望在本地和后端同學(xué)進(jìn)行聯(lián)調(diào)。此時(shí),后端rd的接口地址和你發(fā)生了跨域問題昌屉。這阻止了你們的聯(lián)調(diào)钙蒙,你只能繼續(xù)使用你mock的假數(shù)據(jù)。
解決方案:
CORS需要瀏覽器和服務(wù)器同時(shí)支持间驮。如何支持躬厌?請(qǐng)看瀏覽器對(duì)跨域訪問的判定小節(jié)。
整個(gè)CORS通信過程竞帽,都是瀏覽器自動(dòng)完成扛施,不需要用戶參與。對(duì)于開發(fā)者來說屹篓,CORS通信與同源的AJAX通信沒有差別疙渣,代碼完全一樣。瀏覽器一旦發(fā)現(xiàn)AJAX請(qǐng)求跨源抱虐,就會(huì)自動(dòng)添加一些附加的頭信息昌阿,有時(shí)還會(huì)多出一次附加的請(qǐng)求饥脑,但用戶不會(huì)有感覺恳邀。
因此,實(shí)現(xiàn)CORS通信的關(guān)鍵是服務(wù)器灶轰。只要服務(wù)器實(shí)現(xiàn)了CORS接口谣沸,就可以跨源通信。
服務(wù)器要給接口的響應(yīng)頭設(shè)置:
Access-Control-Allow-Origin:*
三笋颤、jsonp
場(chǎng)景:跨域發(fā)送get請(qǐng)求
jsonp解決跨域問題的本質(zhì): <script> 標(biāo)簽可以請(qǐng)求不同域名下的資源乳附,即 <script> 請(qǐng)求不受瀏覽器同源策略影響。
//首先給body動(dòng)態(tài)添加一個(gè) <script>
var script = document.createElement('script');
script.setAttribute("type","text/javascript");
script.src = 'http://example.com/ip?callback=foo';
document.body.appendChild(script);
function foo(data) {
console.log('Your public IP address is: ' + data.ip);
};
- 上面的script會(huì)向 http://example.com/ 服務(wù)器發(fā)送請(qǐng)求伴澄,這個(gè)請(qǐng)求的url后面帶了個(gè)callback參數(shù)赋除,是用來告訴服務(wù)器回調(diào)方法的方法名的。因?yàn)榉?wù)器收到請(qǐng)求后非凌,會(huì)把相應(yīng)數(shù)據(jù)寫進(jìn)foo的參數(shù)位置举农,也就是說服務(wù)器會(huì)返回的腳本如下
foo({
"ip": "8.8.8.8"
});
- 這樣瀏覽器通過<script>下載的資源就是上面的腳本了,<script>下載完成就會(huì)立即執(zhí)行敞嗡,也就是說http://example.com/ip?callback=foo這個(gè)請(qǐng)求返回后就會(huì)立即執(zhí)行上面的腳本代碼颁糟,而這個(gè)腳本代碼就是調(diào)用回調(diào)方法和拿到j(luò)son數(shù)據(jù)了。
四喉悴、document.domain跨域
場(chǎng)景1:你的http://www.damonare.cn/a.html頁面里使用<iframe>調(diào)用另一個(gè)http://damonare.cn/b.html頁面棱貌。這時(shí)候你想在a頁面里獲取b頁面里的dom,然后進(jìn)行操作箕肃。然后你會(huì)發(fā)現(xiàn)你不能獲得b的dom婚脱。document.getElementById("myIFrame").contentWindow.document或window.parent.document.body因?yàn)閮蓚€(gè)窗口不同源而報(bào)錯(cuò)。
解決方案:這時(shí)候你只需要在a頁面里和b頁面里把document.domain設(shè)置成相同的值就可以在兩個(gè)頁面里操作Dom了。
場(chǎng)景2:你在http://www.damonare.cn/a.html頁面里寫入了document.cookie = "test1=hello";你在http://damonare.cn/b.html頁面是拿不到這個(gè)cookie的障贸。
解決方案:Cookie 是服務(wù)器寫入瀏覽器的一小段信息涡贱,只有同源的網(wǎng)頁才能共享。但是惹想,兩個(gè)網(wǎng)頁一級(jí)域名相同问词,只是二級(jí)域名不同,瀏覽器允許通過設(shè)置document.domain共享 Cookie嘀粱。另外激挪,服務(wù)器也可以在設(shè)置Cookie的時(shí)候,指定Cookie的所屬域名為一級(jí)域名锋叨。這樣的話垄分,二級(jí)域名和三級(jí)域名不用做任何設(shè)置,都可以讀取這個(gè)Cookie娃磺。
-
注意:
- document.domain限制:雖然可讀寫薄湿,但只能設(shè)置成自身或者是高一級(jí)的父域且主域必須相同。所以只能解決一級(jí)域名相同二級(jí)域名不同的跨域問題偷卧。
- document.domain只適用于 Cookie 和 iframe 窗口豺瘤,LocalStorage 和 IndexDB 無法通過這種方法跨域。
五听诸、window.name跨域
場(chǎng)景1:現(xiàn)在瀏覽器的一個(gè)標(biāo)簽頁里打開http://www.damonare.cn/a.html頁面坐求,你通過location.href=http://baidu.com/b.html,在同一個(gè)瀏覽器標(biāo)簽頁里打開了不同域名下的頁面晌梨。這時(shí)候這兩個(gè)頁面你可以使用window.name來傳遞參數(shù)桥嗤。因?yàn)?a target="_blank">window.name指的是瀏覽器窗口的名字,只要瀏覽器窗口相同仔蝌,那么無論在哪個(gè)網(wǎng)頁里訪問值都是一樣的泛领。
場(chǎng)景2:你的http://www.damonare.cn/a.html頁面里使用<iframe>調(diào)用另一個(gè)http://baidu.com/b.html頁面。這時(shí)候你想在a頁面里獲取b頁面里的dom敛惊,然后進(jìn)行操作渊鞋。然后你會(huì)發(fā)現(xiàn)你不能獲得b的dom。同樣會(huì)因?yàn)椴煌炊鴪?bào)錯(cuò)豆混,和上面提到的不同之處就是兩個(gè)頁面的一級(jí)域名也不相同篓像。這時(shí)候document.domain就解決不了了。
解決方案:瀏覽器窗口有window.name屬性皿伺。這個(gè)屬性的最大特點(diǎn)是员辩,無論是否同源,只要在同一個(gè)窗口里鸵鸥,前一個(gè)網(wǎng)頁設(shè)置了這個(gè)屬性奠滑,后一個(gè)網(wǎng)頁可以讀取它丹皱。。比如你在b頁面里設(shè)定window.name="hello"宋税,你再返回到a頁面摊崭,在a頁面里訪問window.name,可以得到hello杰赛。
這種方法的優(yōu)點(diǎn)是呢簸,window.name容量很大,可以放置非常長(zhǎng)的字符串乏屯;缺點(diǎn)是必須監(jiān)聽子窗口window.name屬性的變化根时,影響網(wǎng)頁性能。
六辰晕、postMessage方法跨域
場(chǎng)景1:在a頁面里打開了另一個(gè)不同源的頁面b蛤迎,你想要讓a和b兩個(gè)頁面互相通信。比如含友,a要訪問b的LocalStorage替裆。
場(chǎng)景2:你的a頁面里的iframe的src是不同源的b頁面,你想要讓a和b兩個(gè)頁面互相通信窘问。比如辆童,a要訪問b的LocalStorage。
解決方案:HTML5y引入了一個(gè)全新的API南缓,跨文檔通信 API(Cross-document messaging)胸遇。這個(gè)API為window對(duì)象新增了一個(gè)window.postMessage方法,允許跨窗口通信汉形,不論這兩個(gè)窗口是否同源。a就可以把它的LocalStorage倍阐,發(fā)送給b概疆,b也可以把自己的LocalStorage發(fā)給a。
window.postMessage(message, targetOrigin, [transfer])峰搪,有三個(gè)參數(shù):
- message是向目標(biāo)窗口發(fā)送的數(shù)據(jù)岔冀;
- targetOrigin屬性來指定哪些窗口能接收到消息事件,其值可以是字符串"*"(表示無限制)或者一個(gè)URI(或者說是發(fā)送消息的目標(biāo)域名)概耻;
- transfer可選參數(shù)使套,是一串和message 同時(shí)傳遞的 Transferable 對(duì)象. 這些對(duì)象的所有權(quán)將被轉(zhuǎn)移給消息的接收方,而發(fā)送一方將不再保有所有權(quán)鞠柄。
- 另外消息的接收方必須有監(jiān)聽事件侦高,否則發(fā)送消息時(shí)就會(huì)報(bào)錯(cuò)。
The target origin provided ('http://localhost:8080') does not match the recipient window's origin ('http://localhost:63343').
window.addEventListener("message",onmessage);onmessage接收到的message事件包含三個(gè)屬性:
- data:從其他 window 中傳遞過來的數(shù)據(jù)厌杜。
- origin:調(diào)用 postMessage 時(shí)消息發(fā)送方窗口的 origin 奉呛。請(qǐng)注意计螺,這個(gè)origin不能保證是該窗口的當(dāng)前或未來origin,因?yàn)閜ostMessage被調(diào)用后可能被導(dǎo)航到不同的位置瞧壮。
- source:對(duì)發(fā)送消息的窗口對(duì)象的引用; 您可以使用此來在具有不同origin的兩個(gè)窗口之間建立雙向通信登馒。
- 例子:我在a頁面執(zhí)行
var popup = window.open('http://localhost:3000', 'title');
popup.postMessage('Hello World!', 'http://localhost:3000');`
同時(shí)在http://localhost:3000的頁面里監(jiān)聽message事件:
window.onload=function () {
window.addEventListener("message",onmessage);
}
function onmessage(event) {
if(event.origin=="http://localhost:63343"){//http://localhost:63343是發(fā)送方a的域名
console.log(event.data);//'Hello World!'
}
console.log(event.data);//'Hello World!'
}
注意: 在 Gecko 6.0 (Firefox 6.0 / Thunderbird 6.0 / SeaMonkey 2.3)之前, 參數(shù) message 必須是一個(gè)字符串咆槽。 從 Gecko 6.0 (Firefox 6.0 / Thunderbird 6.0 / SeaMonkey 2.3)開始陈轿,參數(shù) message被使用結(jié)構(gòu)化克隆算法進(jìn)行序列化。這意味著您可以將各種各樣的數(shù)據(jù)對(duì)象安全地傳遞到目標(biāo)窗口秦忿,而不必自己序列化它們济欢。
七、location.hash跨域
location.hash就是指URL的#號(hào)后面的部分小渊。
場(chǎng)景:
父窗口和iframe的子窗口之間通訊或者是window.open打開的子窗口之間的通訊法褥。解決方案:
父窗口改變子窗口的url的#號(hào)后面的部分,后者把要傳遞的參數(shù)寫在#后面酬屉,子窗口監(jiān)聽window.onhashchange事件半等,得到通知,讀取window.location.hash解析出有用的數(shù)據(jù)呐萨。同樣子窗口也可以向父窗口傳遞數(shù)據(jù)杀饵。