CORS即Cross-Origin Resource Sharing,跨域資源共享
CORS分爲兩種
一:簡單的跨域請求,流程如下
網頁:當HTTP請求同時滿足以下兩種情況時,瀏覽器認爲是簡單跨請求
1),請求的方法是get,head或者post,同時Content-Type是application/x-www-form-urlencoded, multipart/form-data 或 text/plain中的一個值,或者不設置也可以,一般默認就是application/x-www-form-urlencoded。
2),請求中沒有自定義的HTTP頭部,如x-token。(應該是這幾種頭部 Accept,Accept-Language,Content-Language,Last-Event-ID,Content-Type)
瀏覽器:把客戶端腳本所在的域填充到Origin header裏,向其他域的服務器請求資源。
服務器:根據資源權限配置,在響應頭中添加Access-Control-Allow-Origin Header,返回結果
瀏覽器:比較服務器返回的Access-Control-Allow-Origin Header和請求域的Origin,如果當前域已經得到授權,則將結果返回給頁面。否則瀏覽器忽略此次響應。
網頁:收到返回結果或者瀏覽器的錯誤提示。
總結:對於簡單的跨域請求,只要服務器設置的Access-Control-Allow-Origin Header和請求來源匹配,瀏覽器就允許跨域。服務器端設置的Access-Control-Allow-Methods和Access-Control-Allow-Headers對簡單跨域沒有作用。
二:帶預檢(Preflighted)的跨域請求,流程如下
網頁:當HTTP請求出現以下兩種情況時之一,瀏覽器認爲是帶預檢(Preflighted)的跨域請求:
1),請求的方法不是 GET, HEAD或者POST三種,或者是這三種,但是Content-Type不是application/x-www-form-urlencoded, multipart/form-data或text/plain中的一種。
2),請求中有自定義HTTP頭部。
瀏覽器:發現請求屬於以上兩種情況,向服務器發送一個OPTIONS預檢請求,檢測服務器端是否支持真實請求進行跨域資源訪問,瀏覽器會在發送OPTIONS請求時會自動添加Origin Header 、Access-Control-Request-Method Header和Access-Control-Request-Headers Header。
服務器:響應OPTIONS請求,會在responseHead裏添加Access-Control-Allow-Methods head。這其中的method的值是服務器給的默認值,可能不同的服務器添加的值不一樣。服務器還會添加Access-Control-Allow-Origin Header和Access-Control-Allow-Headers Header。這些取決於服務器對OPTIONS請求具體如何做出響應。如果服務器對OPTIONS響應不合你的要求,你可以手動在服務器配置OPTIONS響應,以應對帶預檢的跨域請求。在配置服務器OPTIONS的響應時,可以添加Access-Control-Max-Age head告訴瀏覽器在一定時間內無需再次發送預檢請求,但是如果瀏覽器禁用緩存則無效。
瀏覽器:接到OPTIONS的響應,比較真實請求的method是否屬於返回的Access-Control-Allow-Methods head的值之一,還有origin, head也會進行比較是否匹配。如果通過,瀏覽器就繼續向服務器發送真實請求。 否則就會報預檢錯誤,如下幾種:
請求來源不被options響應允許: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.
請求方法不被options響應允許:Method PUT is not allowed by Access-Control-Allow-Methods in preflight response.
請求中有自定義header不被options響應允許: Failed to load... Request header field myHeader is not allowed by Access-Control-Allow-Headers in preflight response.
服務器:響應真實請求,在響應頭中放入Access-Control-Allow-Origin Header、Access-Control-Allow-Methods和Access-Control-Allow-Headers Header,分別表示允許跨域資源請求的域、請求方法和請求頭,並返回數據。(如果服務器對真實請求的響應另外設置有Access-Control-Allow-Methods,它的值不會生效,個人理解是因爲剛剛在服務器響應OPTIONS響應時,就已經驗證過真實請求的method是屬於Access-Control-Allow-Methods head的值之一)。也可以在響應真實請求時添加Access-Control-Max-Age head。
瀏覽器:接受服務器對真實請求的返回結果,返回給網頁
網頁:收到返回結果或者瀏覽器的錯誤提示。
總結:也就是說Access-Control-Allow-Methods和Access-Control-Allow-Headers只在響應options請求時有作用,Access-Control-Allow-Origin在響應options請求和響應真實請求時都是有作用的,兩者必須同時包含要跨域的源。
服務器設置OPTIONS響應一般要同時滿足這些條件,一是跨域,二是有帶預檢的請求,三是服務器對OPTIONS響應默認值不符合要求,如果是不存在跨域情況,就不需要在服務器手動設置OPTIONS響應。
XMLHttpRequest支持通過withCredentials屬性跨域請求攜帶身份信息(Credential,例如Cookie或者HTTP認證信息)。瀏覽器將攜帶Cookie Header的請求發送到服務器端後,如果服務器沒有響應Access-Control-Allow-Credentials Header,那麼瀏覽器會忽略掉這次響應。如果服務器設置Access-Control-Allow-Credentials爲true,那麼就不能再設置Access-Control-Allow-Origin爲*,必須用具體的域名。
express框架跨域設置:
// 以node-express框架爲例
const app = require('express')();
app.options('/', (req, res) => {
//express框架有res.set()和res.header()兩種方式設置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請求"
}
res.send(obj)
})
app.post('/', (req, res) => {
console.log('post請求')
res.set({
"Access-Control-Allow-Origin": "http://127.0.0.1:8080",
// 'Access-Control-Allow-Methods': 'POST',//無需設置。因爲如果是帶預檢的跨域請求時,是否是允許的該請求方法取決於options請求響應時的response head裏的access-control-allow-methods head.如果是簡單的跨域請求,只有Access-Control-Allow-Origin會參與匹配,此設置依然沒有作用。
// 'Access-Control-Allow-Headers': 'x-requested-with, accept, origin, content-type,A',//不需設置,原因同上。
});
const obj = {
"msg": "post請求"
}
res.send(obj)
})
app.get('/', (req, res, next) => {
console.log('get請求')
res.set({
"Access-Control-Allow-Origin": "http://127.0.0.1:8080",
});
const obj = {
msg: 'get請求'
}
res.send(obj)
})
app.put('/', (req, res) => {
res.set({
"Access-Control-Allow-Origin": "http://127.0.0.1:8080"
});
const obj = {
"msg": "put請求"
}
res.send(obj)
})
app.listen(3333, function () {
console.log('express start at port 3333')
})
koa框架跨域設置:
//以node-koa框架爲例
const Koa = require('koa');
const app = new Koa();
const _cors=(ctx,next)=>{
//指定服務器端允許進行跨域資源訪問的來源域。可以用通配符*表示允許任何域的JavaScript訪問資源,但是在響應一個攜帶身份信息(Credential)的HTTP請求時,必需指定具體的域,不能用通配符
ctx.set("Access-Control-Allow-Origin", "http://127.0.0.1:8080");
//指定服務器允許進行跨域資源訪問的請求方法列表,一般用在響應預檢請求上
ctx.set("Access-Control-Allow-Methods", "OPTIONS,POST,GET,HEAD,DELETE,PUT");
//必需。指定服務器允許進行跨域資源訪問的請求頭列表,一般用在響應預檢請求上
ctx.set("Access-Control-Allow-Headers", "x-requested-with, accept, origin, content-type");
//告訴客戶端返回數據的MIME的類型,這只是一個標識信息,並不是真正的數據文件的一部分
ctx.set("Content-Type", "application/json;charset=utf-8");
//可選,單位爲秒,指定瀏覽器在本次預檢請求的有效期內,無需再發送預檢請求進行協商,直接用本次協商結果即可。當請求方法是PUT或DELETE等特殊方法或者Content-Type字段的類型是application/json時,服務器會提前發送一次請求進行驗證
ctx.set("Access-Control-Max-Age", 300);
//可選。它的值是一個布爾值,表示是否允許客戶端跨域請求時攜帶身份信息(Cookie或者HTTP認證信息)。默認情況下,Cookie不包括在CORS請求之中。當設置成允許請求攜帶cookie時,需要保證"Access-Control-Allow-Origin"是服務器有的域名,而不能是"*";如果沒有設置這個值,瀏覽器會忽略此次響應。
ctx.set("Access-Control-Allow-Credentials", true);
//可選。跨域請求時,客戶端xhr對象的getResponseHeader()方法只能拿到6個基本字段,Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。要獲取其他字段時,使用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={"請求方式":_method};
};
app.use(_cors)
app.use(main)
app.listen(5000, function () {
console.log('koa start at port 5000')
})
// 前臺代碼,用jqAjax和axios兩種請求方式對比
<!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" οnclick='changeServer("http://localhost:3333",this)'>express_3333</button></th>
<th><button id="koa" class="server" οnclick='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" οnclick='changeContentType("application/x-www-form-urlencoded",this)'>application/x-www-form-urlencoded</button></td>
<td><button class="ContentType" οnclick='changeContentType("multipart/form-data",this)'>multipart/form-data</button></td>
<td><button class="ContentType" οnclick='changeContentType("text/plain",this)'>text/plain</button></td>
<td><button class="ContentType" οnclick='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 οnclick='jq_request("GET")'>jq_get</button></td>
<td><button οnclick='axios_request("GET")'>axios_get</button></td>
</tr>
<tr>
<td><button οnclick='jq_request("HEAD")'>jq_head</button></td>
<td><button οnclick='axios_request("HEAD")'>axios_head</button></td>
</tr>
<tr>
<td><button οnclick='jq_request("POST")'>jq_post</button></td>
<td><button οnclick='axios_request("POST")'>axios_post</button></td>
</tr>
<tr>
<td><button οnclick='jq_request("PUT")'>jq_put</button></td>
<td><button οnclick='axios_request("PUT")'>axios_put</button></td>
</tr>
<tr>
<td><button οnclick='jq_request("DELETE")'>jq_delete</button></td>
<td><button οnclick='axios_request("DELETE")'>axios_delete</button></td>
</tr>
<tr>
<td><button οnclick='jq_request("OPTIONS")'>jq_options</button></td>
<td><button οnclick='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
https://www.jianshu.com/p/5b3acded5182
歡迎補充,討論。轉載或引用請註明出處。