前置知識(shí)
跨域:瀏覽器發(fā)送的請(qǐng)求地址(URL)與所在頁(yè)面的地址 不同(端口/協(xié)議/域名 其一不同)。簡(jiǎn)言之,瀏覽器發(fā)出的請(qǐng)求url,與其所在頁(yè)面的url,不一樣。此時(shí),同源機(jī)制會(huì)讓瀏覽器拒收 服務(wù)器響應(yīng)回來(lái)的數(shù)據(jù)。
同源機(jī)制:瀏覽器出于安全方面的考慮,只允許與本域下的接口交互。不同源的客戶端腳本在沒(méi)有明確授權(quán)的情況下,不能讀寫(xiě)對(duì)方的資源。(這是 瀏覽器發(fā)送請(qǐng)求的安全機(jī)制,和服務(wù)器無(wú)關(guān),即使服務(wù)器返回了請(qǐng)求,此機(jī)制也會(huì)讓瀏覽器拒收)
(同源策略 場(chǎng)景舉例:1發(fā)請(qǐng)求--瀏覽器在一個(gè)網(wǎng)站上,需要發(fā)送請(qǐng)求到該網(wǎng)站域名上的數(shù)據(jù),是可以的,但若請(qǐng)求不是朝該網(wǎng)站域名下的資源,就不可以。也可以修改自己瀏覽器的跨域設(shè)置,但用戶不會(huì)修改的。2 iframe:一個(gè)網(wǎng)站發(fā)iframe打開(kāi)了另一個(gè)非同源的網(wǎng)站,但不能操作iframe里的網(wǎng)站的任何資源,如js,否則會(huì)危害這個(gè)iframe打開(kāi)的網(wǎng)站)
本域(同源)指的是(下面三者都具備)?
同協(xié)議:如都是http或者h(yuǎn)ttps
同域名:如都是http://jirengu.com/a 和http://jirengu.com/b
同端口:如都是80端口
本文參考了下面三個(gè)博客,尤其第一個(gè)非常好,推薦用印象筆記剪藏后查閱,三種方式的具體實(shí)現(xiàn),推薦可參考第一篇:
https://segmentfault.com/a/1190000011145364 (推薦)
https://github.com/hungeraibin/blog/issues/39
https://github.com/hungeraibin/blog/issues/38
三種解決方式
JSONP
JSONP(JSON with padding(添加))是通過(guò) script 標(biāo)簽加載數(shù)據(jù)的方式,去獲取數(shù)據(jù),并把數(shù)據(jù) 當(dāng)做 JS 代碼來(lái)執(zhí)行:提前在頁(yè)面上聲明一個(gè)函數(shù),函數(shù)名通過(guò)接口傳參的方式傳給后臺(tái),后臺(tái)解析到函數(shù)名后在原始數(shù)據(jù)上「包裹」這個(gè)函數(shù)名,發(fā)送給前端。換句話說(shuō),JSONP 需要對(duì)應(yīng)接口的后端的配合才能實(shí)現(xiàn)。
jsonp缺點(diǎn):只能實(shí)現(xiàn)get一種請(qǐng)求。JSONP太駭客,不如下面的CORS正統(tǒng)。
//把響應(yīng)的json數(shù)據(jù)放入到回調(diào)函數(shù)的變量中
<script src="http://api.jirengu.com/weather.php?callback=showData"></script>
這個(gè)請(qǐng)求到達(dá)后端后,后端會(huì)去解析callback這個(gè)參數(shù),獲取到字符串showData,在發(fā)送數(shù)據(jù)做如下處理(換言之,前端和后端約定好,讓后端支持這么處理):
之前后端返回?cái)?shù)據(jù): {"city": "hangzhou", "weather": "晴天"} ,現(xiàn)在后端返回?cái)?shù)據(jù): showData({"city": "hangzhou", "weather": "晴天"}) (相當(dāng)于一個(gè)函數(shù))。 前端script標(biāo)簽在加載數(shù)據(jù)后,會(huì)把 「showData({“city”: “hangzhou”, “weather”: “晴天”})」做為 js 來(lái)執(zhí)行,這實(shí)際上就是調(diào)用showData這個(gè)函數(shù),同時(shí)參數(shù)是 {“city”: “hangzhou”, “weather”: “晴天”}。 用戶只需要在加載 提前在頁(yè)面定義好showData這個(gè)全局函數(shù),在函數(shù)內(nèi)部處理參數(shù)即可。
CORS
CORS 全稱是 跨域資源共享(Cross-Origin Resource Sharing),是一種 ajax 跨域請(qǐng)求資源的方式,支持現(xiàn)代瀏覽器,IE支持10以上。 實(shí)現(xiàn)方式很簡(jiǎn)單,
1 當(dāng)你使用 XMLHttpRequest 發(fā)送請(qǐng)求時(shí),瀏覽器發(fā)現(xiàn)該請(qǐng)求不符合同源策略,會(huì)給該請(qǐng)求加一個(gè)請(qǐng)求頭:Origin;(瀏覽器無(wú)需做設(shè)置 只需發(fā)送ajax)
2 后臺(tái)服務(wù)器收到請(qǐng)求后,會(huì)進(jìn)行一系列處理,如果確定接受請(qǐng)求,則在返回結(jié)果中加入一個(gè)響應(yīng)頭:Access-Control-Allow-Origin (換言之,服務(wù)器允許的域url,會(huì)加入此響應(yīng)頭,相當(dāng)于一個(gè)憑證);
3 瀏覽器判斷該相應(yīng)頭中是否包含 Origin 的值,如果有,則瀏覽器會(huì)處理響應(yīng),我們就可以拿到響應(yīng)數(shù)據(jù),如果不包含,瀏覽器直接駁回,這時(shí)我們無(wú)法拿到響應(yīng)數(shù)據(jù)。
所以 CORS 的表象是讓你覺(jué)得它與同源的 ajax 請(qǐng)求沒(méi)啥區(qū)別,代碼完全一樣。
postMessage(著重,代碼演示)
通過(guò)讓iframe的js元素,執(zhí)行postmessage(),以發(fā)送消息給iframe訪問(wèn)的url網(wǎng)站,如果網(wǎng)站服務(wù)器收到消息(即 監(jiān)聽(tīng)到了 以該消息為名 的事件)并進(jìn)行處理,則瀏覽器可以實(shí)現(xiàn)一定程度的跨域。
瀏覽器發(fā)送數(shù)據(jù) window.frames[0].postMessage(this.value,'*');
服務(wù)器監(jiān)聽(tīng)跨域請(qǐng)求&瀏覽器監(jiān)聽(tīng)返回的數(shù)據(jù) window.addEventListener('message',function(e) {})
//參考文章【1】
postMessage是HTML5 XMLHttpRequest Level 2中的API,且是為數(shù)不多可以跨域操作的window屬性之一,它可用于解決以下方面的問(wèn)題:
a.) 頁(yè)面和其打開(kāi)的新窗口的數(shù)據(jù)傳遞
b.) 多窗口之間消息傳遞
c.) 頁(yè)面與嵌套的iframe消息傳遞
d.) 上面三個(gè)場(chǎng)景的跨域數(shù)據(jù)傳遞
用法:postMessage(data,origin)方法接受兩個(gè)參數(shù)
data: html5規(guī)范支持任意基本類(lèi)型或可復(fù)制的對(duì)象,但部分瀏覽器只支持字符串,所以傳參時(shí)最好用JSON.stringify()序列化。
origin: 協(xié)議+主機(jī)+端口號(hào),也可以設(shè)置為"*",表示可以傳遞給任意窗口,如果要指定和當(dāng)前窗口同源的話設(shè)置為"/"。
1.)a.html:(http://www.domain1.com/a.html)
<iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe><script>
var iframe = document.getElementById('iframe');
iframe.onload = function() {
var data = {
name: 'aym'
};
// 向domain2傳送跨域數(shù)據(jù)
iframe.contentWindow.postMessage(JSON.stringify(data), 'http://www.domain2.com');
};
// 接受domain2返回?cái)?shù)據(jù)
window.addEventListener('message', function(e) {
alert('data from domain2 ---> ' + e.data);
}, false);
</script>
2.)b.html:(http://www.domain2.com/b.html)
<script>
// 接收domain1的數(shù)據(jù)
window.addEventListener('message', function(e) {
alert('data from domain1 ---> ' + e.data);
var data = JSON.parse(e.data);
if (data) {
data.number = 16;
// 處理后再發(fā)回domain1
window.parent.postMessage(JSON.stringify(data), 'http://www.domain1.com');
}
}, false);
</script>