生存指南之 CORS + API Gateway

本文介紹了跨域資源共享的基本知識(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í)行以下操作:

  1. 要處理 preflight requests,在每個(gè) HTTP 端點(diǎn)中添加 enableCORS: trueintegratedResponse: 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
  1. 要處理 CORS headers,請(qǐng)?jiān)陧憫?yīng)中返回 CORS headers。主要標(biāo)頭是 Access-Control-Allow-OriginAccess-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);
};

這里需要注意 responseheaders 屬性,其中包含 Access-Control-Allow-OriginAccess-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ò)誤啦!??


傳送門:

歡迎訪問:Serverless 中文網(wǎng),您可以在 最佳實(shí)踐 里體驗(yàn)更多關(guān)于 Serverless 應(yīng)用的開發(fā)!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 引用自HTTP訪問控制(CORS) 當(dāng) Web 資源請(qǐng)求由其它域名或端口提供的資源時(shí),會(huì)發(fā)起跨域 HTTP 請(qǐng)求(...
    有涯逐無涯閱讀 2,690評(píng)論 0 4
  • > 翻譯:瘋狂的技術(shù)宅 ### 前言 CORS 與 cookie 在前端是個(gè)非常重要的問題,不過在大多數(shù)情況下,因...
    京程一燈閱讀 797評(píng)論 0 1
  • 本文來自于公眾號(hào)鏈接: 徹底掌握CORS跨源資源共享 本文接上篇公眾號(hào)文章:徹底理解瀏覽器同源策略SOP 一.概...
    趙召閱讀 529評(píng)論 0 1
  • 本人博客文章地址:點(diǎn)擊進(jìn)入 今天在寫一個(gè)簡單的 mock-server 的時(shí)候遇到了跨域問題,導(dǎo)致前端頁面不能正常...
    Madokami閱讀 2,981評(píng)論 0 2
  • CORS是一個(gè)W3C標(biāo)準(zhǔn),全稱是"跨域資源共享"(Cross-origin resource sharing)。 ...
    奇特思維家閱讀 1,183評(píng)論 0 3

友情鏈接更多精彩內(nèi)容