AWS API Gateway+Lambda基本使用、檔案上傳至S3

2022/11/12

簡易串接

 

建立Lambda Function

首先建立一個基本的Lambda Function

使用Node.js預設範本

加上console.log印出請求的event物件

exports.handler = async (event) => {
    console.warn('start lambda')
    console.log(event)
    const response = {
        statusCode: 200,
        body: JSON.stringify('Hello from Lambda!'),
    };
    return response;
};

 

建立API Gateway

建立好API後

設定一個資源(這邊範例資源為file)

並設定一個Method(POST)

然後這個Method將導向前面設定的Lambda Function

1-setup-method.png

 

發布API

API Gateway設定好之後

必須使用"部署API"按鈕才能將API發佈出去

2-deploy-test.png

 

發佈的時候必須設定一個stage(這邊設定為dev)

3-deploy-stage.png

 

取得API URL

前往剛佈署好的stage

點擊要取得的Method

右邊即可查看該API的URL

4-check-url.png

 

發測試請求(node.js)

 

範例程式碼

import axios from 'axios'

const start = async () => {
  let result = null
  try {
    result = await axios({
      baseURL: 'https://api-gateway-base-url',
      url: '/dev/file',
      method: 'post',
      data: {
        foo: 'bar',
      },
    })
    console.log('result', result.data)
  } catch (error) {
    console.warn(error.response.status, error.response.data)
  }
}

start()

 

執行此node.js程式碼後

將會透過console.log印出結果

result { statusCode: 200, body: '"Hello from Lambda!"' }

 

查看Lambda Log

透過Cloudwatch的Lambda Log

可看到Lambda內console印出的字串跟params data

5-lambda-request.png

 

到這一步驟為止

已經算是簡易的設定API Gateway串接Lambda了

些下來要做一些進階的設定

 

透過API Key驗證請求

有些API需要特別經過驗證

這時候可透過API Gateway最簡單的驗證方式 - API Key來做驗證

 

Usage Plan

是一種控管API的方式

使用Usage Plan加上API Key配給一個Client

可控管該Client對API的用量以及存取權

 

建立Usage Plan

6-usage-plan-create.png

 

設定關聯的stage

7-usage-plan-link-stage.png

 

設定API Key

可在建立Usage Plan的時候一併建立

或是先建立API Key再設定至Usage Plan

8-create-api-key.png

 

將Method啟用API Key功能

啟用後需要幾秒鐘的時間才會生效

9-use-api-key.png

 

測試請求

用先前的node.js請求程式碼再次測試

將會因為錯誤進到catch中

將response的status跟content印出來(結果如下)

403 { message: 'Forbidden' }

 

將測試請求程式碼加上API Key

前往API Key頁面選擇要查看的API Key

點擊顯示即可取得API Key

10-copy-apikey.png

 

調整請求程式碼

在header新增x-api-key並帶入剛才取得的API Key

即可通過API Key的驗證

axios({
  headers: {
    'x-api-key': 'your-api-key'
  },
})

 

處理檔案上傳請求

ref

API Gateway有支援二進位檔案上傳請求

此段落示範如何由API Gateway允許檔案上傳請求

並在Lambda接收檔案並寫入至S3中

 

API Gateway設定

前往API Gateway的設定中

Binary Media Types中設定要判定為二進位檔案的Content-Type

當API Gateway偵測到設定的Content-Type

就會自動判定為二進位檔案請求來處理

1-api-gateway-binary-media-type.png

 

API Gateway Integration Request設定

接著還要至上傳的API Integration Request中設定Mapping Template

Mapping Template可以用來設定遇到指定Content-Type的時候

API Gateway要將Client的請求轉換為指定的請求物件(也就是傳遞至Lambda的event物件)

因此這邊要設定各種要判定為上傳檔案的Content-Type類型

以此範例來說

我希望Lambda接到的event物件內有二進位檔案內容、所有的請求資料

因此這邊設定content為"$input.body"來接二進位檔案

params則是請求的所有資料

#set($allParams = $input.params())

{
  "content": "$input.body",
  "params" : {
    #foreach($type in $allParams.keySet())
    #set($params = $allParams.get($type))
    "$type" : {
      #foreach($paramName in $params.keySet())
      "$paramName" : "$util.escapeJavaScript($params.get($paramName))"
      #if($foreach.hasNext),#end
      #end
    }
    #if($foreach.hasNext),#end
    #end
  }
}

 

1-api-gateway-mapping-template.png

 

改好之後要記得重新Deploy API才會生效

 

使用Lambda先觀察event物件

這邊使用Lambda印出event物件來觀察Mapping Template設定後的結果

exports.handler = async (event) => {
  console.log(event)
  const response = {
    statusCode: 200,
    body: JSON.stringify('Hello from Lambda!'),
  };
  return response;
};

 

Client端發出請求後

查看Cloudwatch的Lambda執行紀錄

就會看到event物件有content跟params兩個剛才在Mapping Template中設定的欄位

params中可以看到請求的相關資料都包在裡面

2-inspect-request.png

 

調整Lambda Role的權限(允許寫入檔案至S3)

因為這邊要將檔案寫入至S3

因此要先調整Lambda的Execute Role(Lambda的Configuration > Permssion內可找到Role)

直接在Role的Permssion設定中使用"Create inline policy"(內嵌政策)來設定權限即可

3-setup-permission.png

 

Policy JSON如下

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:GetObject",
                "s3:PutObjectAcl"
            ],
            "Resource": "arn:aws:s3:::{Bucket-Name}/*"
        }
    ]
}

 

調整Lambda, 開始處理檔案並寫入至S3

將Lambda設定此Node.js程式碼

即可透過Lambda Role使用S3 client library將檔案寫入至S3

ACL設定可依照需求決定(ref)

ContentType若沒設定將使用預設值"application/octet-stream"(用瀏覽器開啟將會直接下載檔案)

Body使用Buffer.from去讀取二進位的檔案內容

const AWS = require('aws-sdk')
const S3 = new AWS.S3()

const upload = async (contentType, filename, content) => {
  const params = {
    Bucket: '{Bucket-Name}',
    Key: `folder/${filename}`,
    Body: Buffer.from(content, 'base64'), // 讀取二進位的檔案內容
    ContentType: contentType,
    ACL: 'public-read', // 依照需求設定
  }
  try {
    const result = await S3.putObject(params).promise()
    console.log(result)
  } catch (error) {
    console.error(error)
  }
}

exports.handler = async (event) => {
  // 由client端header filename來決定存進s3的檔名
  const filename = event.params.header.filename

  // 由client端header content-type來決定存進s3的content type
  const contentType = event.params.header['Content-Type'] || event.params.header['content-type']

  await upload(contentType, filename, event.content)
  const response = {
    statusCode: 200,
    body: JSON.stringify('Hello from Lambda!'),
  };
  return response;
};

 

Client如何發二進位檔案上傳請求

 

Node.js

透過fs.readfilesync method讀取檔案

const axios = require('axios')
const fs = require('fs')

const start = async () => {
  let result = null
  const file = fs.readFileSync('/path/to/photo.png')
  try {
    result = await axios({
      baseURL: 'https://api.gateway.endpoint',
      url: '/{stage-name}/{upload-api-route}',
      method: 'post',
      data: file,
      headers: {
        'x-api-key': '{api-key}',
        'filename': (new Date()).getTime()+'.png',
        'Content-Type': 'image/png',
      },
    })
    console.log('result', result.data)
  } catch (error) {
    console.warn(error.response.status, error.response.data)
  }
}

start()

 

JavaScript(Client)

html

<script src="https://code.jquery.com/jquery-3.6.1.min.js" integrity="sha256-o88AwQnZB+VDvE9tvIXrMQaPlFFSUTR+nldQm1LuPXQ=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/1.1.3/axios.min.js"></script>

<input id="file-browser" accept="image/*" type="file">
<button id="upload-button">upload</button>

 

js

const upload = async (file) => {
  let result = null
  try {
    result = await axios({
      baseURL: 'https://api.gateway.endpoint',
      url: '/{stage-name}/{upload-api-route}',
      data: file,
      headers: {
        'x-api-key': '{api-key}',
        'filename': (new Date()).getTime()+'.png',
        'Content-Type': 'image/png',
      },
    })
    console.log('result', result.data)
  } catch (error) {
    console.warn(error.response.status, error.response.data)
  }
}
$(function () {
  $('#upload-button').click(function () {
    const file = $('#file-browser')[0].files[0]
    if(!file) return
    upload(file)
  })
})

 

Postman

Header設定

6-postman-1.png

 

檔案設定(Body > binary)

6-postman-2.png

 

CORS設定

若要使用瀏覽器發請求

API必須啟用CORS設定

5-cors.png

 

若有像上面範例用一些自訂Header像是filename

Access-Control-Allow-Headers必須要加入自訂的header

 

使用Cloudwatch來紀錄API Gateway請求

ref

如果要紀錄API Gateway的請求來做Debug或其他用途

就可使用Cloudwatch來做紀錄

 

這邊要建立一個IAM Role給API Gateway使用

讓它可以有權限寫入Cloudwatch Log

權限可使用預設的Permission "AmazonAPIGatewayPushToCloudWatchLogs"

 

Step 1 - 選擇服務

服務選擇API Gateway

7-create-role-step1.png

 

Step 2 - 設定權限

直接選擇預設的"AmazonAPIGatewayPushToCloudWatchLogs"即可

接著下一步設定好Role名稱之後就可以直接建立Role

7-create-role-step2.png

Step3 - 在API設定中加入Role ARN

8-cloudwatch.png

 

Step 4.1 - 所有API啟用Log

可在整個Stage設定中啟用Cloudwatch Logs功能

並可依照需求決定要紀錄的程度

9-enable-log.png

 

Step 4.2 - Method個別設定Log

可在Method中覆寫Stage的Log設定

 

10-enable-log.png

 

Troubleshooting

 

403 - Missing Authentication Token

路由錯誤(API不存在)

 

403 - Forbidden

API Key驗證失敗

 

415 - Unsupported Media Type

在指定的content-type啟用binary media type後

如果送出的請求body不是二進位檔案

就會出現此類型的錯誤

 

400 - Could not parse request body into json

error-400.png

 

這點跟415 - Unsupported Media Type這個錯誤剛好相反

這是API沒將該content-type設定為binary media type

但請求body又送出binary檔案

導致API Gateway直接把body當成json解析出現的錯誤

 

遇到這個錯誤看是要把API改成binary類型

或是改成client請求不要送binary檔案即可