生存指南之 CORS + API Gateway

本文介紹了跨域資源共享的基本知識,以及如何避免雲函數上 Serverless web API 的問題


生存指南之 CORS + API Gateway

構建 Web API 是 Serverless 應用中最流行的用例之一,您能在不增加其他操作開銷的情況下,獲得簡單、可擴展的後端優勢。

然而,如果您的網頁正在調用後端 API,那麼必須處理 跨域資源共享 (CORS) 的問題 ,如果您的網頁向與您當前所在域的不同域發出 HTTP 請求,則它必須是 CORS 友好的。

如果您發現以下錯誤:

No 'Access-Control-Allow-Origin' header is present on the requested resource

那麼本文可能對您有所幫助。

接下來,我們將介紹 Serverless + CORS 的相關信息,目錄如下:

  • 預檢請求 (Preflight requests)
  • 響應頭 (Response headers)
  • CORS with cookie credentials

TL;DR

快速開始 如果您想快速解決 Serverless 應用中的 CORS,可以執行以下操作:

  1. 要處理 preflight requests,在每個 HTTP 端點中添加 enableCORS: true 和 integratedResponse: true 標記:
<code># 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 服務 ID
serviceId: service-xxx
httpMethod: GET
# 開啟集成相應,這裡必須開啟,才能自定義響應 headers
integratedResponse: true,
# 開啟 CORS
enableCORS: true
createProduct:
handler: handler.createProduct
events:
- apigw:
name: api
parameters:
path: /product
stageName: release
# 修改成你的 API 服務 ID
serviceId: service-xxx
httpMethod: POST
# 開啟集成相應,這裡必須開啟,才能自定義響應 headers
integratedResponse: true,
# 開啟 CORS
enableCORS: false/<code>
  1. 要處理 CORS headers,請在響應中返回 CORS headers。主要標頭是 Access-Control-Allow-Origin 和 Access-Control-Allow-Credentials。示例如下:
<code>'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);
};/<code>

CORS preflight requests

如果您不是在進行「simple request」,那麼瀏覽器會用 OPTIONS 方法,發送 preflight request 到資源裡,您請求的資源將使用 安全發送到資源 的方法返回,並且可以選擇返回有效發送的標頭。

我們拆開來看看:

瀏覽器什麼時候發送 preflight requests?

您的瀏覽器會對幾乎所有的跨域請求發送一個 preflight requests。(例外是「simple requests」,但這只是請求的一小部分)。大體上看,一個簡單請求只是一個 GET request 或者 POST request,如果您不在此範圍內,則需要進行預檢。

對 preflight requests 的響應是什麼?

對一個 preflight requests 的 響應包括其允許訪問的資源,它允許在該資源的方法,如 GET, POST, PUT 等。還可以包括被允許在該資源標頭,如 Authentication。

如何處理 Serverless 中的 preflight requests?

要設置 preflight requests,您只需要在 API Gateway 的端點上配置一個 OPTIONS 。幸運的是,你可以非常簡單地使用 Serverless Framework 來完成。

只需要在 serverless.yml 添加設置 enableCORS: true:

<code># 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
# 開啟集成相應,這裡必須開啟,才能自定義響應 headers
integratedResponse: true,
# 開啟 CORS
enableCORS: true
createProduct:
handler: handler.createProduct
events:
- apigw:
name: api
parameters:
path: /product
stageName: release
serviceId: service-lanyfiga
httpMethod: POST
# 開啟集成相應,這裡必須開啟,才能自定義響應 headers

integratedResponse: true,
# 開啟 CORS
enableCORS: false/<code>

CORS Response Headers

儘管 preflight request 僅適用於某些跨域請求,但每個跨域請求中都必須存在 CORS Response Headers,這意味著您必須將 Access-Control-Allow-Origin 添加進 handlers 的響應中。

如果您使用 cookies,還需要添加 Access-Control-Allow-Credentials。

要與上面的 serverless.yml 匹配,handler.js 文件應該如下設置:

<code>// 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);
};/<code>

這裡需要注意 response 的 headers 屬性,其中包含 Access-Control-Allow-Origin 和 Access-Control-Allow-Credentials。

下面是一個簡單的示例:

<code>// 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 };/<code>

CORS with Cookie credentials

在上面的示例中,我們給定了 "*" 作為 Access-Control-Allow-Origin 的值。但是,如果您使用 request using credentials 則不被允許。為了使瀏覽器能夠響應,Access-Control-Allow-Origin 需要包含發出請求的特定來源。有兩種方法可以解決。

首先,如果只有一個發出請求的原始網站,則可以將其硬編碼到雲函數的響應中:

<code>// 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', // 'Access-Control-Allow-Credentials': true,
},
body: JSON.stringify({
product: product,
}),
};

callback(null, response);
};/<code>

如果有多個原始網站使用您的 API,那麼需要採用一種更加動態的方法。你可以檢查 origin header 看看是否在被批准的來源列表中,如果是,則在 Access-Control-Allow-Origin 返回原點值。

<code>// 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);
};/<code>

在這個示例中,我們檢查 origin header 是否匹配。如果匹配,我們會在 Access-Control-Allow-Origin 包含特定來源,並聲明 Access-Control-Allow-Credentials 允許的來源。如果 origin 不是我們允許的來源之一,則我們將包含標準 headers,如果來源嘗試進行憑據請求,則將被拒絕。

小結

處理 CORS 確實是一件麻煩的事情,但是使用 Serverless Framework 會讓處理步驟變得簡單得多!而這也就意味著再也不會出現 No 'Access-Control-Allow-Origin' header is present on the requested resource 這樣的錯誤啦!


傳送門:

GitHub: github.com/serverless 官網:serverless.com

歡迎訪問:Serverless 中文網,您可以在 最佳實踐 裡體驗更多關於 Serverless 應用的開發!


分享到:


相關文章: