瀏覽器的同源策略
瀏覽器出于安全方面的考慮危纫,只允許與本域下的接口交互。不同源的客戶端腳本在沒有明確授權(quán)的情況下掰派,不能讀寫對方的資源从诲。
本域指的是?
- 同協(xié)議:如都是http或者h(yuǎn)ttps
- 同域名:如都是http://zeeliu.com/a 和http://zeeliu.com/b(要求一字不差)
- 同端口:如都是80端口
如:
http://zeeliu.com/a/b.js 和 http://zeeliu.com/index.php (同源)
不同源的例子:
http://zeeliu.com/main.js 和 https://zeeliu.com/a.php (協(xié)議不同)
http://zeeliu.com/main.js 和 http://bbs.zeeliu.com/a.php (域名不同靡羡,域名必須完全相同才可以)
http://zeeliu.com/main.js 和 http://zeeliu.com:8080/a.php (端口不同,第一個是80)
四中跨域方法
JSONP
JSONP是服務(wù)器與客戶端跨源通信的常用方法系洛。最大特點就是簡單適用俊性,老式瀏覽器全部支持,服務(wù)器改造非常小描扯。
html中script標(biāo)簽可以引入其他域下的js定页,比如引入線上的jquery庫。利用這個特性绽诚,可實現(xiàn)跨域訪問接口典徊。需要后端支持
- 定義數(shù)據(jù)處理函數(shù):_fn
- 創(chuàng)建script標(biāo)簽,src的地址執(zhí)行后端接口憔购,最后加個參數(shù)callback=_fun
- 服務(wù)端在收到請求后宫峦,解析參數(shù),計算返還數(shù)據(jù)玫鸟,輸出 fun(data) 字符串导绷。
- fun(data)會放到script標(biāo)簽做為js執(zhí)行。此時會調(diào)用fun函數(shù)屎飘,將data做為參數(shù)妥曲。
代碼如下
index.html代碼:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>jsonp跨域</title>
<style>
.container {
width: 500px;
margin: 0 auto;
}
</style>
</head>
<body>
<div class="container">
<ul class="news">
<li>海軍首批空中女戰(zhàn)勤加入戰(zhàn)斗序列</li>
<li>運(yùn)20完成首次人員空運(yùn)試驗:乘客均為研發(fā)團(tuán)隊成員</li>
<li>英媒稱中國將引領(lǐng)科技變革:新四大發(fā)明走向全球</li>
</ul>
<button class="change">換一組</button>
</div>
<script>
$('.change').addEventListener('click', function(){
//點擊按鈕后創(chuàng)建一個<script>標(biāo)簽;且標(biāo)簽的src=“http://127.0.0.1:8080/getNews?callback=appendHtml”
var script = document.createElement('script')
script.src = 'http://127.0.0.1:8080/getNews?callback=appendHtml';
document.head.appendChild(script) //把這個標(biāo)簽插入頭部钦购;就會發(fā)送src請求
document.head.removeChild(script) //發(fā)送請求后檐盟,這個script標(biāo)簽就沒用了,就立即刪除押桃;
})
//由于發(fā)送的請求里面有關(guān)鍵字callback=appendHtml
//所以當(dāng)后臺處理請求時候會把數(shù)據(jù)用這個關(guān)鍵詞和括號鏈接如:“appendHtml(數(shù)據(jù))”
//下面這個函數(shù)就是用來解析數(shù)據(jù)的葵萎;
function appendHtml(news){
var html = ''
for(var i=0; i<news.length; i++){
html += '<li>' + news[i] + '</li>'
}
console.log(html)
$('.news').innerHTML = html
}
//封裝的document選擇器
function $(selector){
return document.querySelector(selector)
}
</script>
</body>
</html>
router.js的代碼
// 注意這里的代碼要放在單獨(dú)的 router.js 文件中
router.get('/getNews', function (req, res) {
var news = [
"海軍首批空中女戰(zhàn)勤加入戰(zhàn)斗序列",
"運(yùn)20完成首次人員空運(yùn)試驗:乘客均為研發(fā)團(tuán)隊成員",
"英媒稱中國將引領(lǐng)科技變革:新四大發(fā)明走向全球",
"印巴軍隊交火未平息 兩國代表聯(lián)合國又上演舌戰(zhàn)",
"朝鮮的這樣?xùn)|西比“地震”更加震動美國人",
"鄭家概履新武警部隊參謀長 接替秦天",
"從仕途起點清除遺毒 這個落馬副部問題多嚴(yán)重?",
"蔡英文'樸實'午宴曝光:等于退休人員全家4天菜錢"
]
var data = []
for (var i = 0; i < 3; i++) {
var index = parseInt(Math.random() * news.length)
data.push(news[index])
news.splice(index, 1) //為了避免隨機(jī)到重復(fù)元素唱凯,所以push完一個就刪除當(dāng)前
}
var cb = req.query.callback
if (cb) {
res.send(cb + '(' + JSON.stringify(data) + ')')
} else {
res.send(data)
}
})
上面index.html中<script src="http://127.0.0.1:8080/getNews?callback=appendHtml">
標(biāo)簽的src就是一個請求
把這串地址在瀏覽器打開可以看到返回的數(shù)據(jù)如下圖:
其實其他域名地址下的js就是利用這調(diào)串?dāng)?shù)據(jù)調(diào)用寫好的js函數(shù)實現(xiàn)解析數(shù)據(jù)羡忘;實現(xiàn)跨域。
CORS
參考文章:阮一峰-跨域資源共享 CORS 詳解
CORS是一個W3C標(biāo)準(zhǔn)磕昼,全稱是"跨域資源共享"(Cross-origin resource sharing)卷雕。
它允許瀏覽器向跨源服務(wù)器,發(fā)出XMLHttpRequest
請求票从,從而克服了AJAX只能同源使用的限制漫雕。
本文詳細(xì)介紹CORS的內(nèi)部機(jī)制。
簡介
CORS需要瀏覽器和服務(wù)器同時支持峰鄙。
目前浸间,所有瀏覽器都支持該功能,IE瀏覽器不能低于IE10吟榴。
整個CORS通信過程发框,都是瀏覽器自動完成,不需要用戶參與。對于開發(fā)者來說梅惯,CORS通信與同源的AJAX通信沒有差別,代碼完全一樣仿野。瀏覽器一旦發(fā)現(xiàn)AJAX請求跨源铣减,就會自動添加一些附加的頭信息,有時還會多出一次附加的請求脚作,但用戶不會有感覺葫哗。
因此,實現(xiàn)CORS通信的關(guān)鍵是服務(wù)器球涛。只要服務(wù)器實現(xiàn)了CORS接口劣针,就可以跨源通信。
基本流程
就是說當(dāng)我發(fā)送請求瀏覽器會默認(rèn)幫請求添加一個請求頭如圖下所示
(這都是由瀏覽器自己完成)
上面的頭信息中亿扁,Origin字段用來說明捺典,本次請求來自哪個源(協(xié)議 + 域名 + 端口)。服務(wù)器根據(jù)這個值从祝,決定是否同意這次請求襟己。
因此只要后端代碼里面同意著了來源的網(wǎng)站請求數(shù)據(jù)就可以了!
后代碼只要在原來的基礎(chǔ)上加一個響應(yīng)頭表示我同意這個來源的網(wǎng)站向我請求數(shù)據(jù)(如下圖)
此時http://api.bob.com
這個網(wǎng)站就可以跨域向這個后端請求數(shù)據(jù)了
CORS與JSONP的比較
CORS與JSONP的使用目的相同牍陌,但是比JSONP更強(qiáng)大擎浴。
JSONP只支持GET請求,CORS支持所有類型的HTTP請求毒涧。JSONP的優(yōu)勢在于支持老式瀏覽器贮预,以及可以向不支持CORS的網(wǎng)站請求數(shù)據(jù)。
降域
由于第一次看視頻沒看懂契讲;最終原因是里面新出現(xiàn)的標(biāo)簽不了解這里順便把新的知識點也記錄一下仿吞;
首先什么是降域?
首先我們知道瀏覽器會阻止兩個不同的域名之間進(jìn)行數(shù)據(jù)訪問操作怀泊;
但是當(dāng)我有一個域名為:zeeliu.com 還有兩個子域名:a.zeeliu.com和b.zeeliu.com
由于瀏覽器的特性a.zeeliu.com和b.zeeliu.com之間也是不能互相訪問數(shù)據(jù)的(同一個爸爸也沒用)
但是現(xiàn)實情況是我們希望這兩個域名之間互相訪問
所以就有了一個`document.domain`的api
例如:
a.zeeliu.com下的文件問index.html
b.zeeliu.com下的文件問index.html
在兩個index.html下同時寫入js代碼document.domain="zeeliu.com"
這是域名都降為zeeliu.com
所以網(wǎng)頁a.zeeliu.com(a.zeeliu.com:80/index.html)
和網(wǎng)頁b.zeeliu.com(b.zeeliu.com:80/index.html)
之間就可以跨域數(shù)據(jù)交互了
下面我們通過代碼在瀏覽器中測試
由于我們在同一個文件夾下測試所以兩個index.html文件風(fēng)別用a.html和b.html代替
代碼如下(里面有解釋)
a.html
<body>
<style>
.ct{
width: 910px;
margin: auto;
}
.main{
float: left;
width: 450px;
height: 300px;
border: 1px solid #ccc;
}
.main input{
margin: 20px;
width: 200px;
}
.iframe{
float: right;
}
iframe{
width: 450px;
height: 300px;
border: 1px dashed #ccc;
}
</style>
<!-- //面是樣式從下面開始解讀 -->
<div class="ct">
<h1>使用降域?qū)崿F(xiàn)跨域</h1>
<div class="main">
<input type="text" placeholder="http://a.zeeliu.com:8080/a.html我是輸入框默認(rèn)顯示">
</div>
<!-- //ifame標(biāo)簽可以在當(dāng)前窗口建立一個子窗口茫藏;窗口顯示b.zeeliu.com:8080/b.html這個域名的內(nèi)容 -->
<iframe src="http://b.zeeliu.com:8080/b.html" frameborder="0" ></iframe>
</div>
<script>
//URL: http://a.zeeliu.com:8080/a.html
//添加一個事件輸入框內(nèi)容變化出發(fā)(input事件)
document.querySelector('.main input').addEventListener('input', function(){
console.log(this.value);//在控制臺打印輸入框輸入的內(nèi)容
//window.frames獲取當(dāng)前窗口中的所有子窗口得到一個類數(shù)組對象,其實就是針對選中<ifame>創(chuàng)建的子窗口
//下面的代碼是選中窗口后在通過document.querySelector('input')選中b.html中的輸入框(input)然后把當(dāng)前窗口輸入的值(this.value)賦給b.html中的input
//就是a.html中輸入什么b.html中就顯示什么
window.frames[0].document.querySelector('input').value = this.value;
})
//降域
//只有a.html和b.html中同時降域為zeeliu.com上面的input事件中的賦值操作才生效
//也就是a.html通過跨域操作了b.html中的內(nèi)容
document.domain = "zeeliu.com"
</script>
</body>
b.html
<body>
<style>
html,body{
margin: 0;
}
input{
margin: 20px;
width: 200px;
}
</style>
<input id="input" type="text" placeholder="http://b.zeeliu.com:8080/b.html我是輸入框默認(rèn)顯示">
<script>
// URL: http://b.zeeliu.com:8080/b.html
document.querySelector('#input').addEventListener('input', function(){
//同a.html中相同這邊這邊輸入框輸入上面同樣賦值給那邊
//window.parent選擇當(dāng)前前窗口的父窗口一般只有一個
window.parent.document.querySelector('input').value = this.value;
})
//降域
document.domain = 'zeeliu.com';
</script>
</body>
看上面的代碼基本可以知道降域是怎么回事了霹琼;
下面是如何在本地建立服務(wù)器測試這個代碼(包括修改hosts的內(nèi)容)
首先把a(bǔ).html和b.html放在同一個文件夾下
通過前面學(xué)到修改hosts的方法添加兩個域名(如下圖)
然后在當(dāng)前文件夾
mock start
創(chuàng)建模擬服務(wù)器
沒有降域情況下(document.domain被注釋)
如下圖
輸入的地址為a.zeeliu.com:8080/a.html
虛線框窗口引用的地址是b.zeeliu.com:8080/b.html
所以左邊輸入aaaa右邊沒有變化
【這是域不同 且沒有降域】
如下圖
當(dāng)輸入的地址為b.zeeliu.com:8080/a.html及時沒用降域也能實現(xiàn)左右同步
【這是因為他們的域相同】
降域情況下
如下圖
通過document.domain降域后及時域名不同也可以實現(xiàn)效果
postMessage--------------------------------------
其實就是在不降域的情況a.html向b.html發(fā)送自己想給b.html的內(nèi)容务傲;然后b.html在接受這個內(nèi)容(你情我愿);反正b向a也是一樣
看代碼
a.html代碼
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>降域</title>
<style>
.ct{
width: 910px;
margin: auto;
}
.main{
float: left;
width: 450px;
height: 300px;
border: 1px solid #ccc;
}
.main input{
margin: 20px;
width: 200px;
}
.iframe{
float: right;
}
iframe{
width: 450px;
height: 300px;
border: 1px dashed #ccc;
}
</style>
</head>
<body>
<div class="ct">
<h1>使用postMessage實現(xiàn)跨域</h1>
<div class="main">
<input type="text" placeholder="http://a.zeeliu.com:8080/a.html">
</div>
<iframe src="http://b.zeeliu.com:8080/b.html" frameborder="0" ></iframe>
</div>
<script>
//當(dāng)輸入框內(nèi)發(fā)生變化觸發(fā)事件
$('.main input').addEventListener('input', function(){
console.log(this.value);
//將當(dāng)前窗口的值通過.postMessage發(fā)送給window.frames[0]所選中的窗口
//this.value是要發(fā)送的值枣申;(可以使其他的值)
//'*':代表任何網(wǎng)站售葡;(當(dāng)輸入b.zeeliu.com:8080/b.html則只發(fā)給這個域名)
window.frames[0].postMessage(this.value,'*');
})
//這個事件監(jiān)聽從b.html發(fā)送過來的數(shù)據(jù);
//e.data就是接收到的數(shù)據(jù)(b.html中的.postMessage()發(fā)送過來的)
window.addEventListener('message',function(e) {
$('.main input').value = e.data
console.log(e.data);
});
function $(id){
return document.querySelector(id);
}
</script>
</body>
</html>
b.html代碼
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>降域</title>
<style>
html,body{
margin: 0;
}
input{
margin: 20px;
width: 200px;
}
</style>
</head>
<body>
<input id="input" type="text" placeholder="http://b.zeeliu.com:8080/b.html">
<script>
$('#input').addEventListener('input', function(){
//將當(dāng)前窗口的值通過.postMessage發(fā)送給window.parent所選中的窗口
//this.value是要發(fā)送的值忠藤;(可以使其他的值)
//'*':代表任何網(wǎng)站挟伙;(當(dāng)輸入a.zeeliu.com:8080/a.html則只發(fā)給這個域名)
window.parent.postMessage(this.value, '*');
})
//這個事件監(jiān)聽從a.html發(fā)送過來的數(shù)據(jù);
//e.data就是接收到的數(shù)據(jù)(a.html中的.postMessage()發(fā)送過來的)
window.addEventListener('message',function(e) {
$('#input').value = e.data
console.log(e.data);
});
function $(id){
return document.querySelector(id);
}
</script>
</body>
</html>