1. 程式人生 > 實用技巧 >跨域和CORS配置

跨域和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-urlencodedmultipart/form-datatext/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);