跨域和CORS配置
1.為什麼需要CORS
我們都知道瀏覽器有同源策略,即一個域名的網頁去請求另一個域名的資源時協議、域名、埠必須相同。如果有一個不同就跨域了。
現在使用最廣的AJXA預設只支援同源(form表單提交沒有這個這個限制,所以form表單是不會跨域的)
如果前端頁面和後臺介面不是放在同一個伺服器下面的,怎樣解決跨域問題呢?這時候就要用到CORS了
CORS是一個W3C標準,全稱是"跨域資源共享"(Cross-origin resource sharing)。它允許瀏覽器向跨源伺服器,發出XMLHttpRequest
請求,從而克服了AJAX只能同源使用的限制。
2.怎樣配置CORS
瀏覽器一旦發現AJAX請求跨源,就會自動新增一些附加的頭資訊,即CORS請求,有時還會多出一次附加的請求,所以服務端只需要根據請求頭資訊來配置響應頭資訊就可以了
假如我發了這個AJAX請求
const xhr = new XMLHttpRequest(); xhr.open('POST', 'http://localhost:3000/test', true); // 非同步提交 xhr.setRequestHeader('Content-Type', 'application/json'); // 自定義Content-Type xhr.send(JSON.stringify({ name: 'zwh' })) xhr.onreadystatechange = function () { if (xhr.readyState == 4 && xhr.status == 200) { console.log(xhr.response); } };
服務端需要在收到請求的第一時間做如下配置
res.setHeader('Access-Control-Allow-Origin', '*'); // 允許任何網站的訪問
res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); // 允許請求攜帶的自定義頭 Content-Type
這樣就配置好了嗎?不,還差一步。
因為上面的請求是複雜請求,為複雜請求的CORS請求,會在正式通訊之前,增加一次方法為OPTIONS
的請求,稱為"預檢"請求,看看請求能不能跑通。預檢伺服器是需要做迴應的,否則瀏覽器不會發送真正的請求
先來說說什麼是簡單請求和複雜請求
3.簡單請求和複雜請求
只要同時滿足以下兩個條件,就屬於簡單請求。
(1) 請求方法是以下三種方法之一:
- HEAD
- GET
- POST
(2)HTTP的頭資訊不超出以下幾種欄位:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限於三個值
application/x-www-form-urlencoded
、multipart/form-data
、text/plain
所以為什麼上面那個請求是複雜請求?因為AJAX設定了Content-Type: application/json
那怎樣處理預檢請求呢,非常簡單,在配置 CORS 的程式碼後面加上這一段就可以了
if (req.method === 'OPTIONS') {
res.statusCode = 200;
res.end();
}
意思是隻要是預檢請求,通過就可以了。
另外
預檢請求瀏覽器預設是10秒,就是說10秒內的請求只發送一次預檢請求。
Access-Control-Max-Age用來指定本次預檢請求的有效期,單位為秒,一般開發時是設定為30分鐘
res.setHeader("Access-Control-Max-Age", "1800") // 表示隔30分鐘才發起預檢請求
res.setHeader("Access-Control-Max-Age", "0") // 表示每次非同步請求都發起預檢請求
完
前端程式碼
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
const xhr = new XMLHttpRequest();
xhr.open('POST', 'http://localhost:3000/test', true); // 非同步提交
xhr.setRequestHeader('Content-Type', 'application/json'); // 自定義Content-Type
xhr.send(JSON.stringify({ name: 'zwh' }))
xhr.responseType = 'json'; // 瀏覽器把json字串解析成物件
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status == 200) {
console.log(xhr.response);
}
};
// xhr.onload = function () { // xhr.readyState == 4 xhr.status == 200
// console.log(xhr.response)
// }
</script>
</body>
</html>
後臺程式碼
const http = require('http');
const url = require('url');
let server = http.createServer((req, res) => {
let { pathname } = url.parse(req.url);
// 配置Header
res.setHeader('Access-Control-Allow-Origin', req.headers.origin); // 允許任何網站的訪問
res.setHeader('Access-Control-Allow-Headers', 'Content-Type,Authorization'); // 允許請求攜帶的自定義頭Content-Type 和 Authorization
res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT'); // 允許請求的方法。不設定就是預設支援的 get 和 post
res.setHeader("Access-Control-Max-Age", "1800"); // 表示隔30分鐘才發起預檢請求
// 如果是options請求,直接成功
if (req.method === 'OPTIONS') {
res.statusCode = 200;
res.end();
}
// 接收資料
const arr = [];
req.on('data', function (chunk) {
arr.push(chunk);
})
req.on('end', function () {
let result = Buffer.concat(arr).toString();
// result = querystring.parse(result, '&', '=');
if (pathname === '/test' && req.method == 'POST') {
res.end(result);
}
})
})
server.listen(3000);