// 每日前端夜話 第415篇
// 正文共:1900 字
// 預計閱讀時間:8 分鐘

在本文中,咱們將研究怎樣用 Express 配置 CORS 以及根據須要定製 CORS 中間件。前端
什麼是CORS
CORS 是「跨域資源共享」的簡寫。它是一種容許或限制向 Web 服務器上請求資源的機制,具體取決於進行 HTTP 請求的位置。node
這種策略用於保護特定 Web 服務器免受其餘網站或域的訪問。只有容許的域才能訪問服務器中的文件,例如樣式表、圖像或腳本等。web
假設你當前使用的是 http://example.com/page1
,而且你引用的是來自 http://image.com/myimage.jpg
的圖片,那麼除非 http://image.com
容許與 http://example.com
進行跨域共享,不然將沒法獲取該圖像。chrome
每一個 HTTP 請求頭中都有一個名爲 origin
的頭。它定義了域請求的來源。能夠用這個頭的信息來限制引用你服務器上的資源。數據庫
❝默認來自任何其餘來源的請求都會受到瀏覽器的限制。express
❞
例如當開發時若是用的是 React 或 Vue 這類的前端庫,則前端應用將運行在 http://localhost:3000
上,同時,你的 Express 服務器可能正在其餘端口上運行,例如 http://localhost:2020
。這時就須要在這些服務器之間容許 CORS。npm
若是你在瀏覽器控制檯中看到下圖這類的錯誤。問題可能出在 CORS 限制上:json

若是咱們須要提供公共 API 並但願控制對某些資源的訪問和使用方式時,CORS 可以發揮很大的做用。跨域
另外,若是想在其餘網頁上使用本身的 API 或文件,也能夠簡單地將 CORS 配置爲容許本身引用,同時把其餘人拒之門外。瀏覽器
用 Express 配置 CORS
首先建立一個新的項目,並建立目錄結構,而後使用默認設置運行 npm init
:
$ mkdir myapp
$ cd myapp
$ npm init -y
接下來安裝所需的模塊。咱們將使用 express
和 cors
中間件:
$ npm i --save express
$ npm i --save cors
而後,開始建立一個簡單的有兩個路由的 Web 程序,用來演示 CORS 的工做原理。
首先建立一個名爲 index.js 的文件,用來充當 Web 服務器,並實現幾個請求處理函數:
const express = require('express');
const cors = require('cors');
const app = express();
app.get('/', (req, res) => {
res.json({
message: 'Hello World'
});
});
app.get('/:name', (req, res) => {
let name = req.params.name;
res.json({
message: `Hello ${name}`
});
});
app.listen(2020, () => {
console.log('server is listening on port 2020');
});
運行服務器:
$ node index.js
訪問 http://localhost:2020/
服務器應該返回 JSON 消息:
{
"message": "Hello World"
}
訪問 http://localhost:2020/something
應該可以看到:
{
"message": "Hello something"
}
啓用全部CORS請求
若是想爲全部的請求啓用 CORS,能夠在配置路由以前簡單地使用 cors
中間件:
const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors())
......
若是須要,這會容許在網絡上的任何位置訪問全部路由。因此在本例中,每一個域均可以訪問兩條路由。
例如,若是咱們的服務器在 http://www.example.com
上運行並提供諸如圖片之類的內容,則咱們容許 http://www.differentdomain.com
之類的其餘域從 http://www.example.com
進行引。
所以 http://www.differentdomain.com
上的網頁能夠將咱們的域用做圖像的來源:
<img src="http://www.example.com/img/cat.jpg">
爲單個路由啓用 CORS
若是隻須要其中某一個路由,能夠在某個路由中將 cors
配置爲中間件:
app.get('/', cors(), (req, res) => {
res.json({
message: 'Hello World'
});
});
這會容許任何域訪問特定的路由。在當前的狀況下,其餘域都只能訪問 /
路由。僅在與 API(在本例中爲http://localhost:2020
)的相同域中發起的請求才能訪問 /:name
路由。
若是嘗試另外一個來源發送請求到 /
路徑將會成功,而且會收到 Hello World
做爲響應:
fetch('http://localhost:2020/')
.then(response => response.json())
.then(data => console.log(data))
.catch(err => console.error(err));
運行上面的代碼,會看到來自服務器的響應已成功輸出到控制檯:
{
message: 'Hello World'
}
若是訪問除根路徑之外的其餘路徑,例如 http://localhost:2020/name
或 http://localhost:2020/img/cat.png
,則此請求將會被瀏覽器阻止:
fetch('http://localhost:2020/name/janith')
.then(response => response.json())
.then(data => console.log(data))
.catch(err => console.error(err));
若是在其餘 Web 應用中運行代碼,應該看到如下錯誤:

用選項配置CORS
還能夠用自定義選項來配置 CORS。能夠根據須要配置容許的 HTTP 方法,例如 GET
和 POST
。
下面是經過 CORS 選項容許單個域訪問的方法:
var corsOptions = {
origin: 'http://localhost:8080',
optionsSuccessStatus: 200 // For legacy browser support
}
app.use(cors(corsOptions));
若是你在源中配置域名-服務器將容許來自已配置域的CORS。所以,在咱們的例子中,能夠從 http://localhost:8080
訪問該API,並禁止其餘域使用。
若是發送一個 GET 請求,則任何路徑都應該能夠訪問,由於這些選項是在應用在程序級別上的。
運行下面的代碼將請求從 http://localhost:8080
發送到 http://localhost:2020
:
//
fetch('http://localhost:2020/')
.then(response => response.json())
.then(data => console.log(data))
.catch(err => console.error(err));
//
fetch('http://localhost:2020/name/janith')
.then(response => response.json())
.then(data => console.log(data))
.catch(err => console.error(err));
能夠看到被容許從該程序和域中獲取信息。
還能夠根據須要配置容許的 HTTP 方法:
var corsOptions = {
origin: 'http://localhost:8080',
optionsSuccessStatus: 200 // 對於舊版瀏覽器的支持
methods: "GET, PUT"
}
app.use(cors(corsOptions));
若是從 http://localhost:8080
發送POST請求,則瀏覽器將會阻止它,由於僅支持 GET 和 PUT:
fetch('http://localhost:2020', {
method: 'POST',
body: JSON.stringify({name: "janith"}),
})
.then(response => response.json())
.then(data => console.log(data))
.catch(err => console.error(err));
用函數配置動態 CORS 源
若是配置不知足你的要求,也能夠建立函數來定製 CORS。
例如假設要容許 http://something.com 和 http://example.com 對 .jpg
文件進行CORS共享:
const allowlist = ['http://something.com', 'http://example.com'];
const corsOptionsDelegate = (req, callback) => {
let corsOptions;
let isDomainAllowed = whitelist.indexOf(req.header('Origin')) !== -1;
let isExtensionAllowed = req.path.endsWith('.jpg');
if (isDomainAllowed && isExtensionAllowed) {
// 爲此請求啓用 CORS
corsOptions = { origin: true }
} else {
// 爲此請求禁用 CORS
corsOptions = { origin: false }
}
callback(null, corsOptions)
}
app.use(cors(corsOptionsDelegate));
回調函數接受兩個參數,第一個是傳遞 null
的錯誤,第二個是傳遞 { origin: false }
的選項。第二個參數能夠是用 Express 的 request
對象構造的更多選項。
因此 http://something.com
或 http://example.com
上的 Web 應用將可以按照自定義配置從服務器引用擴展名爲 .jpg
的圖片。
這樣能夠成功引用資源文件:
<img src="http://yourdomain.com/img/cat.jpg">
可是下面的文件將會被阻止:
<img src="http://yourdomain.com/img/cat.png">
從數據源加載容許的來源列表做
還能夠用保存在數據庫中的白名單列表或任何一種數據源來容許 CORS:
var corsOptions = {
origin: function (origin, callback) {
// 從數據庫加載容許的來源列表
// 例如:origins = ['http://example.com', 'http//something.com']
database.loadOrigins((error, origins) => {
callback(error, origins);
});
}
}
app.use(cors(corsOptions));





本文分享自微信公衆號 - 前端先鋒(jingchengyideng)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。