本文介紹了跨域資源共享的基本知識(shí),以及如何避免云函數(shù)上 Serverless web API 的問題
構(gòu)建 Web API 是 Serverless 應(yīng)用中最流行的用例之一,您能在不增加其他操作開銷的情況下,獲得簡單、可擴(kuò)展的后端優(yōu)勢。
然而,如果您的網(wǎng)頁正在調(diào)用后端 API,那么必須處理 跨域資源共享 (CORS) 的問題 ,如果您的網(wǎng)頁向與您當(dāng)前所在域的不同域發(fā)出 HTTP 請(qǐng)求,則它必須是 CORS 友好的。
如果您發(fā)現(xiàn)以下錯(cuò)誤:
No 'Access-Control-Allow-Origin' header is present on the requested resource
那么本文可能對(duì)您有所幫助。
接下來,我們將介紹 Serverless + CORS 的相關(guān)信息,目錄如下:
TL;DR
快速開始 ?? 如果您想快速解決 Serverless 應(yīng)用中的 CORS,可以執(zhí)行以下操作:
- 要處理 preflight requests,在每個(gè) HTTP 端點(diǎn)中添加
enableCORS: true和integratedResponse: true標(biāo)記:
# serverless.yml
service: products-service
provider:
name: tencent
region: ap-guangzhou
runtime: Nodejs8.9 # Nodejs8.9 or Nodejs6.10
plugins:
- serverless-tencent-scf
functions:
getProduct:
handler: handler.getProduct
events:
- apigw:
name: api
parameters:
path: /product
stageName: release
# 修改成你的 API 服務(wù) ID
serviceId: service-xxx
httpMethod: GET
# 開啟集成相應(yīng),這里必須開啟,才能自定義響應(yīng) headers
integratedResponse: true,
# 開啟 CORS
enableCORS: true
createProduct:
handler: handler.createProduct
events:
- apigw:
name: api
parameters:
path: /product
stageName: release
# 修改成你的 API 服務(wù) ID
serviceId: service-xxx
httpMethod: POST
# 開啟集成相應(yīng),這里必須開啟,才能自定義響應(yīng) headers
integratedResponse: true,
# 開啟 CORS
enableCORS: false
- 要處理 CORS headers,請(qǐng)?jiān)陧憫?yīng)中返回 CORS headers。主要標(biāo)頭是
Access-Control-Allow-Origin和Access-Control-Allow-Credentials。示例如下:
'use strict';
// mock function
function retrieveProduct(event) {
return {
id: 1,
name: 'good1',
price: 10,
};
}
// mock function
function createProduct(event) {
const { queryString } = event;
return {
id: Number(queryString.id),
name: 'good1',
price: 10,
};
}
module.exports.getProduct = (event, context, callback) => {
const product = retrieveProduct(event);
const response = {
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials': true,
},
body: JSON.stringify({
product: product,
}),
};
callback(null, response);
};
module.exports.createProduct = (event, context, callback) => {
// Do work to create Product
const product = createProduct(event);
const response = {
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials': true,
},
body: JSON.stringify({
product: product,
}),
};
callback(null, response);
};
CORS preflight requests
如果您不是在進(jìn)行「simple request」,那么瀏覽器會(huì)用 OPTIONS 方法,發(fā)送 preflight
request 到資源里,您請(qǐng)求的資源將使用 安全發(fā)送到資源 的方法返回,并且可以選擇返回有效發(fā)送的標(biāo)頭。
我們拆開來看看:
瀏覽器什么時(shí)候發(fā)送 preflight requests?
您的瀏覽器會(huì)對(duì)幾乎所有的跨域請(qǐng)求發(fā)送一個(gè) preflight requests。(例外是「simple requests」,但這只是請(qǐng)求的一小部分)。大體上看,一個(gè)簡單請(qǐng)求只是一個(gè) GET request 或者 POST request,如果您不在此范圍內(nèi),則需要進(jìn)行預(yù)檢。
對(duì) preflight requests 的響應(yīng)是什么?
對(duì)一個(gè) preflight requests 的 響應(yīng)包括其允許訪問的資源,它允許在該資源的方法,如 GET, POST,
PUT 等。還可以包括被允許在該資源標(biāo)頭,如 Authentication。
如何處理 Serverless 中的 preflight requests?
要設(shè)置 preflight requests,您只需要在 API Gateway 的端點(diǎn)上配置一個(gè) OPTIONS 。幸運(yùn)的是,你可以非常簡單地使用 Serverless Framework 來完成。
只需要在 serverless.yml 添加設(shè)置 enableCORS: true:
# serverless.yml
service: products-service
provider:
name: tencent
region: ap-guangzhou
runtime: Nodejs8.9 # Nodejs8.9 or Nodejs6.10
plugins:
- serverless-tencent-scf
functions:
getProduct:
handler: handler.getProduct
events:
- apigw:
name: api
parameters:
path: /product
stageName: release
serviceId: service-lanyfiga
httpMethod: GET
# 開啟集成相應(yīng),這里必須開啟,才能自定義響應(yīng) headers
integratedResponse: true,
# 開啟 CORS
enableCORS: true
createProduct:
handler: handler.createProduct
events:
- apigw:
name: api
parameters:
path: /product
stageName: release
serviceId: service-lanyfiga
httpMethod: POST
# 開啟集成相應(yīng),這里必須開啟,才能自定義響應(yīng) headers
integratedResponse: true,
# 開啟 CORS
enableCORS: false
CORS Response Headers
盡管 preflight request 僅適用于某些跨域請(qǐng)求,但每個(gè)跨域請(qǐng)求中都必須存在 CORS Response Headers,這意味著您必須將 Access-Control-Allow-Origin 添加進(jìn) handlers 的響應(yīng)中。
如果您使用 cookies,還需要添加 Access-Control-Allow-Credentials。
要與上面的 serverless.yml 匹配,handler.js 文件應(yīng)該如下設(shè)置:
// handler.js
'use strict';
module.exports.getProduct = (event, context, callback) => {
// Do work to retrieve Product
const product = retrieveProduct(event);
const response = {
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials': true,
},
body: JSON.stringify({
product: product,
}),
};
callback(null, response);
};
module.exports.createProduct = (event, context, callback) => {
// Do work to create Product
const product = createProduct(event);
const response = {
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials': true,
},
body: JSON.stringify({
product: product,
}),
};
callback(null, response);
};
這里需要注意 response 的 headers 屬性,其中包含 Access-Control-Allow-Origin 和 Access-Control-Allow-Credentials。
下面是一個(gè)簡單的示例:
// hello.js
const middy = require('middy');
const { cors } = require('middy/middlewares');
// This is your common handler, no way different than what you are used to do every day
const hello = (event, context, callback) => {
const response = {
statusCode: 200,
body: 'Hello, world!',
};
return callback(null, response);
};
// Let's "middyfy" our handler, then we will be able to attach middlewares to it
const handler = middy(hello).use(cors()); // Adds CORS headers to responses
module.exports = { handler };
CORS with Cookie credentials
在上面的示例中,我們給定了 "*" 作為 Access-Control-Allow-Origin 的值。但是,如果您使用 request using credentials 則不被允許。為了使瀏覽器能夠響應(yīng),Access-Control-Allow-Origin 需要包含發(fā)出請(qǐng)求的特定來源。有兩種方法可以解決。
首先,如果只有一個(gè)發(fā)出請(qǐng)求的原始網(wǎng)站,則可以將其硬編碼到云函數(shù)的響應(yīng)中:
// handler.js
'use strict';
module.exports.getProduct = (event, context, callback) => {
// Do work to retrieve Product
const product = retrieveProduct(event);
const response = {
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': 'https://myorigin.com', // <-- Add your specific origin here
'Access-Control-Allow-Credentials': true,
},
body: JSON.stringify({
product: product,
}),
};
callback(null, response);
};
如果有多個(gè)原始網(wǎng)站使用您的 API,那么需要采用一種更加動(dòng)態(tài)的方法。你可以檢查 origin header 看看是否在被批準(zhǔn)的來源列表中,如果是,則在 Access-Control-Allow-Origin 返回原點(diǎn)值。
// handler.js
'use strict';
const ALLOWED_ORIGINS = [
'https://myfirstorigin.com',
'https://mysecondorigin.com'
];
module.exports.getProduct = (event, context, callback) => {
const origin = event.headers.origin;
let headers;
if (ALLOWED_ORIGINS.includes(origin) {
headers = {
'Access-Control-Allow-Origin': origin,
'Access-Control-Allow-Credentials': true,
},
} else {
headers = {
'Access-Control-Allow-Origin': '*',
},
}
// Do work to retrieve Product
const product = retrieveProduct(event);
const response = {
isBase64Encoded: false,
statusCode: 200,
headers
body: JSON.stringify({
product: product
}),
};
callback(null, response);
};
在這個(gè)示例中,我們檢查 origin header 是否匹配。如果匹配,我們會(huì)在 Access-Control-Allow-Origin 包含特定來源,并聲明 Access-Control-Allow-Credentials 允許的來源。如果 origin 不是我們允許的來源之一,則我們將包含標(biāo)準(zhǔn) headers,如果來源嘗試進(jìn)行憑據(jù)請(qǐng)求,則將被拒絕。
小結(jié)
處理 CORS 確實(shí)是一件麻煩的事情,但是使用 Serverless Framework 會(huì)讓處理步驟變得簡單得多!而這也就意味著再也不會(huì)出現(xiàn) No 'Access-Control-Allow-Origin' header is present on the requested resource 這樣的錯(cuò)誤啦!??
傳送門:
- GitHub: github.com/serverless
- 官網(wǎng):serverless.com
歡迎訪問:Serverless 中文網(wǎng),您可以在 最佳實(shí)踐 里體驗(yàn)更多關(guān)于 Serverless 應(yīng)用的開發(fā)!