CORS即Cross-Origin Resource Sharing,跨域資源共享
CORS分為兩種
一:簡(jiǎn)單的跨域請(qǐng)求,流程如下
網(wǎng)頁(yè):當(dāng)HTTP請(qǐng)求同時(shí)滿足以下兩種情況時(shí),瀏覽器認(rèn)為是簡(jiǎn)單跨請(qǐng)求
1),請(qǐng)求的方法是get,head或者post,同時(shí)Content-Type是application/x-www-form-urlencoded, multipart/form-data 或 text/plain中的一個(gè)值,或者不設(shè)置也可以,一般默認(rèn)就是application/x-www-form-urlencoded。
2),請(qǐng)求中沒有自定義的HTTP頭部,如x-token。(應(yīng)該是這幾種頭部 Accept,Accept-Language,Content-Language,Last-Event-ID,Content-Type)
瀏覽器:把客戶端腳本所在的域填充到Origin header里,向其他域的服務(wù)器請(qǐng)求資源。
服務(wù)器:根據(jù)資源權(quán)限配置,在響應(yīng)頭中添加Access-Control-Allow-Origin Header,返回結(jié)果
瀏覽器:比較服務(wù)器返回的Access-Control-Allow-Origin Header和請(qǐng)求域的Origin,如果當(dāng)前域已經(jīng)得到授權(quán),則將結(jié)果返回給頁(yè)面。否則瀏覽器忽略此次響應(yīng)。
網(wǎng)頁(yè):收到返回結(jié)果或者瀏覽器的錯(cuò)誤提示。
總結(jié):對(duì)于簡(jiǎn)單的跨域請(qǐng)求,只要服務(wù)器設(shè)置的Access-Control-Allow-Origin Header和請(qǐng)求來源匹配,瀏覽器就允許跨域。服務(wù)器端設(shè)置的Access-Control-Allow-Methods和Access-Control-Allow-Headers對(duì)簡(jiǎn)單跨域沒有作用。
二:帶預(yù)檢(Preflighted)的跨域請(qǐng)求,流程如下
網(wǎng)頁(yè):當(dāng)HTTP請(qǐng)求出現(xiàn)以下兩種情況時(shí)之一,瀏覽器認(rèn)為是帶預(yù)檢(Preflighted)的跨域請(qǐng)求:
1),請(qǐng)求的方法不是 GET, HEAD或者POST三種,或者是這三種,但是Content-Type不是application/x-www-form-urlencoded, multipart/form-data或text/plain中的一種。
2),請(qǐng)求中有自定義HTTP頭部。
瀏覽器:發(fā)現(xiàn)請(qǐng)求屬于以上兩種情況,向服務(wù)器發(fā)送一個(gè)OPTIONS預(yù)檢請(qǐng)求,檢測(cè)服務(wù)器端是否支持真實(shí)請(qǐng)求進(jìn)行跨域資源訪問,瀏覽器會(huì)在發(fā)送OPTIONS請(qǐng)求時(shí)會(huì)自動(dòng)添加Origin Header 、Access-Control-Request-Method Header和Access-Control-Request-Headers Header。
服務(wù)器:響應(yīng)OPTIONS請(qǐng)求,會(huì)在responseHead里添加Access-Control-Allow-Methods head。這其中的method的值是服務(wù)器給的默認(rèn)值,可能不同的服務(wù)器添加的值不一樣。服務(wù)器還會(huì)添加Access-Control-Allow-Origin Header和Access-Control-Allow-Headers Header。這些取決于服務(wù)器對(duì)OPTIONS請(qǐng)求具體如何做出響應(yīng)。如果服務(wù)器對(duì)OPTIONS響應(yīng)不合你的要求,你可以手動(dòng)在服務(wù)器配置OPTIONS響應(yīng),以應(yīng)對(duì)帶預(yù)檢的跨域請(qǐng)求。在配置服務(wù)器OPTIONS的響應(yīng)時(shí),可以添加Access-Control-Max-Age head告訴瀏覽器在一定時(shí)間內(nèi)無需再次發(fā)送預(yù)檢請(qǐng)求,但是如果瀏覽器禁用緩存則無效。
瀏覽器:接到OPTIONS的響應(yīng),比較真實(shí)請(qǐng)求的method是否屬于返回的Access-Control-Allow-Methods head的值之一,還有origin, head也會(huì)進(jìn)行比較是否匹配。如果通過,瀏覽器就繼續(xù)向服務(wù)器發(fā)送真實(shí)請(qǐng)求。 否則就會(huì)報(bào)預(yù)檢錯(cuò)誤,如下幾種:
??請(qǐng)求來源不被options響應(yīng)允許:Failed to load...Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin http://127.0.0.1:8080 is therefore not allowed access.
??請(qǐng)求方法不被options響應(yīng)允許:Method PUT is not allowed by Access-Control-Allow-Methods in preflight response.
??請(qǐng)求中有自定義header不被options響應(yīng)允許:Failed to load... Request header field myHeader is not allowed by Access-Control-Allow-Headers in preflight response.
服務(wù)器:響應(yīng)真實(shí)請(qǐng)求,在響應(yīng)頭中放入Access-Control-Allow-Origin Header、Access-Control-Allow-Methods和Access-Control-Allow-Headers Header,分別表示允許跨域資源請(qǐng)求的域、請(qǐng)求方法和請(qǐng)求頭,并返回?cái)?shù)據(jù)。(如果服務(wù)器對(duì)真實(shí)請(qǐng)求的響應(yīng)另外設(shè)置有Access-Control-Allow-Methods,它的值不會(huì)生效,個(gè)人理解是因?yàn)閯倓傇诜?wù)器響應(yīng)OPTIONS響應(yīng)時(shí),就已經(jīng)驗(yàn)證過真實(shí)請(qǐng)求的method是屬于Access-Control-Allow-Methods head的值之一)。也可以在響應(yīng)真實(shí)請(qǐng)求時(shí)添加Access-Control-Max-Age head。
瀏覽器:接受服務(wù)器對(duì)真實(shí)請(qǐng)求的返回結(jié)果,返回給網(wǎng)頁(yè)
網(wǎng)頁(yè):收到返回結(jié)果或者瀏覽器的錯(cuò)誤提示。
總結(jié):也就是說Access-Control-Allow-Methods和Access-Control-Allow-Headers只在響應(yīng)options請(qǐng)求時(shí)有作用,Access-Control-Allow-Origin在響應(yīng)options請(qǐng)求和響應(yīng)真實(shí)請(qǐng)求時(shí)都是有作用的,兩者必須同時(shí)包含要跨域的源。
服務(wù)器設(shè)置OPTIONS響應(yīng)一般要同時(shí)滿足這些條件,一是跨域,二是有帶預(yù)檢的請(qǐng)求,三是服務(wù)器對(duì)OPTIONS響應(yīng)默認(rèn)值不符合要求,如果是不存在跨域情況,就不需要在服務(wù)器手動(dòng)設(shè)置OPTIONS響應(yīng)。
XMLHttpRequest支持通過withCredentials屬性跨域請(qǐng)求攜帶身份信息(Credential,例如Cookie或者HTTP認(rèn)證信息)。瀏覽器將攜帶Cookie Header的請(qǐng)求發(fā)送到服務(wù)器端后,如果服務(wù)器沒有響應(yīng)Access-Control-Allow-Credentials Header,那么瀏覽器會(huì)忽略掉這次響應(yīng)。如果服務(wù)器設(shè)置Access-Control-Allow-Credentials為true,那么就不能再設(shè)置Access-Control-Allow-Origin為*,必須用具體的域名。
express框架跨域設(shè)置:
// 以node-express框架為例
const app = require('express')();
app.options('/', (req, res) => {
//express框架有res.set()和res.header()兩種方式設(shè)置header,沒有setHeader方法。
res.set({
"Access-Control-Allow-Origin": "http://127.0.0.1:8080",
'Access-Control-Allow-Methods': 'OPTIONS,HEAD,DELETE,GET,PUT,POST',
'Access-Control-Allow-Headers': 'x-requested-with, accept, origin, content-type',
'Access-Control-Max-Age':10000,
'Access-Control-Allow-Credentials':true
});
const obj = {
"msg": "options請(qǐng)求"
}
res.send(obj)
})
app.post('/', (req, res) => {
console.log('post請(qǐng)求')
res.set({
"Access-Control-Allow-Origin": "http://127.0.0.1:8080",
// 'Access-Control-Allow-Methods': 'POST',//無需設(shè)置。因?yàn)槿绻菐ьA(yù)檢的跨域請(qǐng)求時(shí),是否是允許的該請(qǐng)求方法取決于options請(qǐng)求響應(yīng)時(shí)的response head里的access-control-allow-methods head.如果是簡(jiǎn)單的跨域請(qǐng)求,只有Access-Control-Allow-Origin會(huì)參與匹配,此設(shè)置依然沒有作用。
// 'Access-Control-Allow-Headers': 'x-requested-with, accept, origin, content-type,A',//不需設(shè)置,原因同上。
});
const obj = {
"msg": "post請(qǐng)求"
}
res.send(obj)
})
app.get('/', (req, res, next) => {
console.log('get請(qǐng)求')
res.set({
"Access-Control-Allow-Origin": "http://127.0.0.1:8080",
});
const obj = {
msg: 'get請(qǐng)求'
}
res.send(obj)
})
app.put('/', (req, res) => {
res.set({
"Access-Control-Allow-Origin": "http://127.0.0.1:8080"
});
const obj = {
"msg": "put請(qǐng)求"
}
res.send(obj)
})
app.listen(3333, function () {
console.log('express start at port 3333')
})
koa框架跨域設(shè)置:
//以node-koa框架為例
const Koa = require('koa');
const app = new Koa();
const _cors=(ctx,next)=>{
//指定服務(wù)器端允許進(jìn)行跨域資源訪問的來源域。可以用通配符*表示允許任何域的JavaScript訪問資源,但是在響應(yīng)一個(gè)攜帶身份信息(Credential)的HTTP請(qǐng)求時(shí),必需指定具體的域,不能用通配符
ctx.set("Access-Control-Allow-Origin", "http://127.0.0.1:8080");
//指定服務(wù)器允許進(jìn)行跨域資源訪問的請(qǐng)求方法列表,一般用在響應(yīng)預(yù)檢請(qǐng)求上
ctx.set("Access-Control-Allow-Methods", "OPTIONS,POST,GET,HEAD,DELETE,PUT");
//必需。指定服務(wù)器允許進(jìn)行跨域資源訪問的請(qǐng)求頭列表,一般用在響應(yīng)預(yù)檢請(qǐng)求上
ctx.set("Access-Control-Allow-Headers", "x-requested-with, accept, origin, content-type");
//告訴客戶端返回?cái)?shù)據(jù)的MIME的類型,這只是一個(gè)標(biāo)識(shí)信息,并不是真正的數(shù)據(jù)文件的一部分
ctx.set("Content-Type", "application/json;charset=utf-8");
//可選,單位為秒,指定瀏覽器在本次預(yù)檢請(qǐng)求的有效期內(nèi),無需再發(fā)送預(yù)檢請(qǐng)求進(jìn)行協(xié)商,直接用本次協(xié)商結(jié)果即可。當(dāng)請(qǐng)求方法是PUT或DELETE等特殊方法或者Content-Type字段的類型是application/json時(shí),服務(wù)器會(huì)提前發(fā)送一次請(qǐng)求進(jìn)行驗(yàn)證
ctx.set("Access-Control-Max-Age", 300);
//可選。它的值是一個(gè)布爾值,表示是否允許客戶端跨域請(qǐng)求時(shí)攜帶身份信息(Cookie或者HTTP認(rèn)證信息)。默認(rèn)情況下,Cookie不包括在CORS請(qǐng)求之中。當(dāng)設(shè)置成允許請(qǐng)求攜帶cookie時(shí),需要保證"Access-Control-Allow-Origin"是服務(wù)器有的域名,而不能是"*";如果沒有設(shè)置這個(gè)值,瀏覽器會(huì)忽略此次響應(yīng)。
ctx.set("Access-Control-Allow-Credentials", true);
//可選。跨域請(qǐng)求時(shí),客戶端xhr對(duì)象的getResponseHeader()方法只能拿到6個(gè)基本字段,Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。要獲取其他字段時(shí),使用Access-Control-Expose-Headers,xhr.getResponseHeader('myData')可以返回我們所需的值
ctx.set("Access-Control-Expose-Headers", "myData");
next()
}
const main = function (ctx) {
const _method=ctx.request.method;
ctx.response.body={"請(qǐng)求方式":_method};
};
app.use(_cors)
app.use(main)
app.listen(5000, function () {
console.log('koa start at port 5000')
})
// 前臺(tái)代碼,用jqAjax和axios兩種請(qǐng)求方式對(duì)比
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<table>
<tr>
<th col="2">Server</th>
</tr>
<tr>
<th><button id="express" class="server" onclick='changeServer("http://localhost:3333",this)'>express_3333</button></th>
<th><button id="koa" class="server" onclick='changeServer("http://localhost:5000",this)'>koa_5000</button></th>
</tr>
</table>
<table>
<tr>
<th col="4">content-Type</th>
</tr>
<td><button id="x-www-form-urlencoded" class="ContentType" onclick='changeContentType("application/x-www-form-urlencoded",this)'>application/x-www-form-urlencoded</button></td>
<td><button class="ContentType" onclick='changeContentType("multipart/form-data",this)'>multipart/form-data</button></td>
<td><button class="ContentType" onclick='changeContentType("text/plain",this)'>text/plain</button></td>
<td><button class="ContentType" onclick='changeContentType("application/json",this)'>application/json</button></td>
</table>
<table>
<tr>
<tr>
<th col="2">Method</th>
</tr>
<td>JQuery</td>
<td>axios</td>
</tr>
<tr>
<td><button onclick='jq_request("GET")'>jq_get</button></td>
<td><button onclick='axios_request("GET")'>axios_get</button></td>
</tr>
<tr>
<td><button onclick='jq_request("HEAD")'>jq_head</button></td>
<td><button onclick='axios_request("HEAD")'>axios_head</button></td>
</tr>
<tr>
<td><button onclick='jq_request("POST")'>jq_post</button></td>
<td><button onclick='axios_request("POST")'>axios_post</button></td>
</tr>
<tr>
<td><button onclick='jq_request("PUT")'>jq_put</button></td>
<td><button onclick='axios_request("PUT")'>axios_put</button></td>
</tr>
<tr>
<td><button onclick='jq_request("DELETE")'>jq_delete</button></td>
<td><button onclick='axios_request("DELETE")'>axios_delete</button></td>
</tr>
<tr>
<td><button onclick='jq_request("OPTIONS")'>jq_options</button></td>
<td><button onclick='axios_request("OPTIONS")'>axios_options</button></td>
</tr>
</table>
</body>
<script src="https://cdn.bootcss.com/jquery/3.3.0/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/axios/0.18.0/axios.min.js"></script>
<script>
let url='http://localhost:3333';
let contentType="application/x-www-form-urlencoded";
const changeServer=(a,self)=>{
url=a;
$('.server').css('background','#eee')
$(self).css('background','gray')
}
const changeContentType=(e,self)=>{
contentType=e;
$('.ContentType').css('background','#eee')
$(self).css('background','gray')
}
$('#koa').click();
$('#x-www-form-urlencoded').click();
const jq_request = (method) => {
$.ajax({
url: url,
type: method,
contentType:contentType,
dataType: "json",
success: function (data) {
console.log(data)
},
error: function (err) {
console.log(err)
}
})
}
const axios_request=(method)=>{
axios({
method: method,
url: url,
headers: {
'Content-Type':contentType,
},
responseType: 'json',
}).then(res => {
console.log(res.data)
}).catch(error => {
console.log(error);
});
}
</script>
</html>
參考了以下幾篇博客:
https://blog.csdn.net/enter89/article/details/51205752
https://www.cnblogs.com/MrZouJian/p/8568414.html
http://www.lxweimin.com/p/5b3acded5182
歡迎補(bǔ)充,討論。轉(zhuǎn)載或引用請(qǐng)注明出處。