同源策略
瀏覽器出于安全方面的考慮,只允許與本域下的接口交互米同。不同源的客戶端腳本在沒有明確授權(quán)的情況下骇扇,不能讀寫對方的資源。
本域指的是面粮?
- 同協(xié)議:如都是http或者h(yuǎn)ttps
- 同域名:如都是http://jirengu.com/a 和http://jirengu.com/b
- 同端口:如都是80端口
同源:
不同源:
- http://jirengu.com/main.js 和 https://jirengu.com/a.php (協(xié)議不同)
- http://jirengu.com/main.js 和 http://bbs.jirengu.com/a.php (域名不同少孝,域名必須完全相同才可以)
- http://jiengu.com/main.js 和 http://jirengu.com:8080/a.php (端口不同,第一個是80)
需要注意的是: 對于當(dāng)前頁面來說頁面存放的 JS 文件的域不重要,重要的是加載該 JS 頁面所在什么域
什么是跨域熬苍?
跨域就是Ajax請求的地址與當(dāng)前頁面的url不是同源稍走,即要么不同協(xié)議,要么不同域名柴底,要么不同端口婿脸。
舉個例子, 這是一個簡單的Ajax:請求的地址是http://127.0.0.1:8080/getWeather柄驻,
<h1>hello world</h1>
<script>
var xhr = new XMLHttpRequest()
xhr.open('GET', 'http://127.0.0.1:8080/getWeather', true)
xhr.send()
xhr.onload = function(){
console.log(xhr.responseText)
}
</script>
這是一個簡單的服務(wù)器狐树,支持靜態(tài)頁面和路由:
var http = require('http')
var fs = require('fs')
var path = require('path')
var url = require('url')
http.createServer(function(req,res){
var pathObj = url.parse(req.url, true)
console.log(pathObj)
switch (pathObj.pathname) {
case '/getWeather':
res.end(JSON.stringify({beijing: 'sunny'}))
break;
default:
fs.readFile(path.join(__dirname, pathObj.pathname), function(e,data){
if(e){
res.writeHead(404, 'not found')
res.end('<h1>404 NOT FOUND</h1>')
}else {
res.end(data)
}
})
}
}).listen(8080)
這時打開服務(wù)器,運(yùn)行js鸿脓,輸入url:http://localhost:8080/index.html抑钟,會出現(xiàn)如下效果:
這就是跨域,由于請求的地址是http://127.0.0.1:8080/getWeather野哭,而當(dāng)前的頁面是http://localhost:8080/index.html在塔,域名不同,就無法請求到數(shù)據(jù)拨黔。
那跨域是如何產(chǎn)生的蛔溃?
有幾種情況:1.請求根本沒有發(fā)出去,直接被瀏覽器攔截掉了蓉驹。2.請求發(fā)出去了,但服務(wù)器發(fā)現(xiàn)不一樣揪利,服務(wù)器沒有響應(yīng)态兴。3.請求發(fā)出去了,服務(wù)器也響應(yīng)了疟位,但數(shù)據(jù)返回來之后瞻润,瀏覽器發(fā)現(xiàn)不是同源,給攔截掉了。
于是我們給js文件里面加一句話绍撞,如果請求出現(xiàn)了又執(zhí)行發(fā)送了正勒,便是第三種情況。
這時我們重啟服務(wù)器傻铣,刷新頁面章贞,發(fā)現(xiàn)終端有了這句話:
也就是說,請求發(fā)出去后非洲,服務(wù)器收到了鸭限,可是瀏覽器不接受,因為瀏覽器覺得數(shù)據(jù)不安全两踏,換句話說败京,同源和跨域是瀏覽器的安全機(jī)制。
跨域的幾種實現(xiàn)方式
JSONP
因為Ajax直接請求普通文件存在跨域無權(quán)限訪問的問題梦染,不管是靜態(tài)頁面赡麦、動態(tài)網(wǎng)頁、web服務(wù)帕识、WCF泛粹,只要是跨域請求,一律不準(zhǔn)渡冻。
不過我們Web頁面上調(diào)用js文件時則不受是否跨域的影響戚扳,不僅如此,凡是擁有”src”這個屬性的標(biāo)簽都擁有跨域的能力族吻,比如<\script>帽借、<\img>、<\iframe>
那跟JSON又有什么關(guān)系呢超歌?
因為JSON的純字符數(shù)據(jù)格式可以簡潔的描述復(fù)雜數(shù)據(jù)砍艾,更妙的是JSON還被js原生支持,所以在客戶端幾乎可以隨心所欲的處理這種格式的數(shù)據(jù)巍举。
根據(jù)以上講述脆荷,可以寫一個簡單的JSONP:
<script>
function showData(ret){
console.log(ret);
}
</script>
<script src="http://api.jirengu.com/weather.php?callback=showData"></script>
之前后端返回數(shù)據(jù): {"city": "hangzhou", "weather": "晴天"} 現(xiàn)在后端返回數(shù)據(jù): showData({"city": "hangzhou", "weather": "晴天"}) 前端script標(biāo)簽在加載數(shù)據(jù)后會把 「showData({“city”: “hangzhou”, “weather”: “晴天”})」做為 js 來執(zhí)行,這實際上就是調(diào)用showData這個函數(shù)懊悯,同時參數(shù)是 {“city”: “hangzhou”, “weather”: “晴天”}蜓谋。 用戶只需要在加載提前在頁面定義好showData這個全局函數(shù),在函數(shù)內(nèi)部處理參數(shù)即可炭分。
下面看一個具體的:
index.html
<!DOCTYPE html>
<html>
<body>
<div class="container">
<ul class="news">
</ul>
<button class="show">show news</button>
</div>
<script>
$('.show').addEventListener('click', function(){
var script = document.createElement('script');
script.src = 'http://127.0.0.1:8080/getNews?callback=appendHtml';
document.head.appendChild(script);
document.head.removeChild(script);
})
function appendHtml(news){
var html = '';
for( var i=0; i<news.length; i++){
html += '<li>' + news[i] + '</li>';
}
console.log(html);
$('.news').innerHTML = html;
}
function $(id){
return document.querySelector(id);
}
</script>
</html>
server.js
var http = require('http')
var fs = require('fs')
var path = require('path')
var url = require('url')
http.createServer(function(req, res){
var pathObj = url.parse(req.url, true)
switch (pathObj.pathname) {
case '/getNews':
var news = [
"第11日前瞻:中國沖擊4金 博爾特再戰(zhàn)200米羽球",
"正直播柴飚/洪煒出戰(zhàn) 男雙力爭會師決賽",
"女排將死磕巴西桃焕!郎平安排男陪練模仿對方核心"
]
res.setHeader('Content-Type','text/json; charset=utf-8')
if(pathObj.query.callback){
res.end(pathObj.query.callback + '(' + JSON.stringify(news) + ')')
}else{
res.end(JSON.stringify(news))
}
break;
default:
fs.readFile(path.join(__dirname, pathObj.pathname), function(e, data){
if(e){
res.writeHead(404, 'not found')
res.end('<h1>404 Not Found</h1>')
}else{
res.end(data)
}
})
}
}).listen(8080)
效果如下:
[圖片上傳失敗...(image-63fd88-1551773332301)]
點擊后:
[圖片上傳失敗...(image-1b22a7-1551773332302)]
這里我們請求的是:http://127.0.0.1:8080/getNews?callback=appendHtml,但頁面url是http://localhost:8080/index.html捧毛,不同源也能夠請求數(shù)據(jù)观堂。
因為后端做了判斷:
[圖片上傳失敗...(image-d59de2-1551773332302)]
返回的是
appendHtml(["第11日前瞻:中國沖擊4金 博爾特再戰(zhàn)200米羽球","正直播柴飚/洪煒出戰(zhàn) 男雙力爭會師決賽","女排將死磕巴西让网!郎平安排男陪練模仿對方核心"])
然后這個appenHtml函數(shù)再到html文件中執(zhí)行:
function appendHtml(news){
var html = '';
for( var i=0; i<news.length; i++){
html += '<li>' + news[i] + '</li>';
}
console.log(html);
$('.news').innerHTML = html;
}
最后就是我們呈現(xiàn)的效果。
CORS
CORS(Cross-Origin Resource Sharing)
全名:跨域資源共享师痕。是一種 ajax 跨域請求資源的方式溃睹,支持現(xiàn)代瀏覽器,IE支持10以上胰坟。 實現(xiàn)方式很簡單因篇,當(dāng)你使用 XMLHttpRequest 發(fā)送請求時,瀏覽器發(fā)現(xiàn)該請求不符合同源策略腕铸,會給該請求加一個請求頭:Origin惜犀,后臺進(jìn)行一系列處理,如果確定接受請求則在返回結(jié)果中加入一個響應(yīng)頭:Access-Control-Allow-Origin; 瀏覽器判斷該相應(yīng)頭中是否包含 Origin 的值狠裹,如果有則瀏覽器會處理響應(yīng)虽界,我們就可以拿到響應(yīng)數(shù)據(jù),如果不包含瀏覽器直接駁回涛菠,這時我們無法拿到響應(yīng)數(shù)據(jù)莉御。所以 CORS 的表象是讓你覺得它與同源的 ajax 請求沒啥區(qū)別,代碼完全一樣俗冻。
什么意思呢礁叔?看如下代碼:
server.js
var http = require('http')
var fs = require('fs')
var path = require('path')
var url = require('url')
http.createServer(function(req, res){
var pathObj = url.parse(req.url, true)
switch (pathObj.pathname) {
case '/getNews':
var news = [
"第11日前瞻:中國沖擊4金 博爾特再戰(zhàn)200米羽球",
"正直播柴飚/洪煒出戰(zhàn) 男雙力爭會師決賽",
"女排將死磕巴西!郎平安排男陪練模仿對方核心"
]
res.setHeader('Access-Control-Allow-Origin','http://localhost:8080')
//res.setHeader('Access-Control-Allow-Origin','*')
res.end(JSON.stringify(news))
break;
default:
fs.readFile(path.join(__dirname, pathObj.pathname), function(e, data){
if(e){
res.writeHead(404, 'not found')
res.end('<h1>404 Not Found</h1>')
}else{
res.end(data)
}
})
}
}).listen(8080)
index.html
<!DOCTYPE html>
<html>
<body>
<div class="container">
<ul class="news">
</ul>
<button class="show">show news</button>
</div>
<script>
$('.show').addEventListener('click', function(){
var xhr = new XMLHttpRequest()
xhr.open('GET', 'http://127.0.0.1:8080/getNews', true)
xhr.send()
xhr.onload = function(){
appendHtml(JSON.parse(xhr.responseText))
}
})
function appendHtml(news){
var html = ''
for( var i=0; i<news.length; i++){
html += '<li>' + news[i] + '</li>'
}
$('.news').innerHTML = html
}
function $(selector){
return document.querySelector(selector)
}
</script>
</html>
可以看出html文件與Ajax沒有區(qū)別迄薄,只不過在js文件中加了一句話:
res.setHeader('Access-Control-Allow-Origin','http://localhost:8080')
打開服務(wù)器琅关,輸入網(wǎng)址:http://localhost:8080/index.html,效果如下:
[圖片上傳失敗...(image-b32bb5-1551773332302)]
也實現(xiàn)了跨域讥蔽。需要注意的是圖中紅色框的兩個地方涣易。
為了方便理解再看下面這張圖:
[圖片上傳失敗...(image-f05302-1551773332302)]
這就與上方的那段話對應(yīng)上了:
- 當(dāng)你使用 XMLHttpRequest 發(fā)送請求時,瀏覽器發(fā)現(xiàn)該請求不符合同源策略冶伞,會給該請求加一個請求頭:Origin新症,后臺進(jìn)行一系列處理,如果確定接受請求則在返回結(jié)果中加入一個響應(yīng)頭:Access-Control-Allow-Origin; 瀏覽器判斷該相應(yīng)頭中是否包含 Origin 的值响禽,如果有則瀏覽器會處理響應(yīng)徒爹,我們就可以拿到響應(yīng)數(shù)據(jù),如果不包含瀏覽器直接駁回
降域
a.html
<html>
<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.jrg.com:8080/a.html">
</div>
<iframe src="http://b.jrg.com:8080/b.html" frameborder="0" ></iframe>
</div>
<script>
//URL: http://a.jrg.com:8080/a.html
document.querySelector('.main input').addEventListener('input', function(){
console.log(this.value);
window.frames[0].document.querySelector('input').value = this.value;
})
document.domain = "jrg.com"
</script>
</html>
b.html
<html>
<style>
html,body{
margin: 0;
}
input{
margin: 20px;
width: 200px;
}
</style>
<input id="input" type="text" placeholder="http://b.jrg.com:8080/b.html">
<script>
//URL: http://b.jrg.com:8080/b.html
document.querySelector('#input').addEventListener('input', function(){
window.parent.document.querySelector('input').value = this.value;
})
document.domain = 'jrg.com';
</script>
</html>
我們要進(jìn)行的操作是:在http://a.jrg.com/a.html 頁面請求 http://b.jrg.com/b.html芋类。
效果如圖:
不過需要注意的是在a.jrg.com不能夠調(diào)用ifame隆嗅,因為iframes里面是b.jrg.com。
但是在b.jrg.com可以: