AWS API Gateway+Lambda基本使用、檔案上傳至S3
簡易串接
建立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
發布API
API Gateway設定好之後
必須使用"部署API"按鈕才能將API發佈出去
發佈的時候必須設定一個stage(這邊設定為dev)
取得API URL
前往剛佈署好的stage
點擊要取得的Method
右邊即可查看該API的URL
發測試請求(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
到這一步驟為止
已經算是簡易的設定API Gateway串接Lambda了
些下來要做一些進階的設定
透過API Key驗證請求
有些API需要特別經過驗證
這時候可透過API Gateway最簡單的驗證方式 - API Key來做驗證
Usage Plan
是一種控管API的方式
使用Usage Plan加上API Key配給一個Client
可控管該Client對API的用量以及存取權
建立Usage Plan
設定關聯的stage
設定API Key
可在建立Usage Plan的時候一併建立
或是先建立API Key再設定至Usage Plan
將Method啟用API Key功能
啟用後需要幾秒鐘的時間才會生效
測試請求
用先前的node.js請求程式碼再次測試
將會因為錯誤進到catch中
將response的status跟content印出來(結果如下)
403 { message: 'Forbidden' }
將測試請求程式碼加上API Key
前往API Key頁面選擇要查看的API Key
點擊顯示即可取得API Key
調整請求程式碼
在header新增x-api-key並帶入剛才取得的API Key
即可通過API Key的驗證
axios({
headers: {
'x-api-key': 'your-api-key'
},
})
處理檔案上傳請求
API Gateway有支援二進位檔案上傳請求
此段落示範如何由API Gateway允許檔案上傳請求
並在Lambda接收檔案並寫入至S3中
API Gateway設定
前往API Gateway的設定中
在Binary Media Types中設定要判定為二進位檔案的Content-Type
當API Gateway偵測到設定的Content-Type
就會自動判定為二進位檔案請求來處理
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
}
}
改好之後要記得重新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中可以看到請求的相關資料都包在裡面
調整Lambda Role的權限(允許寫入檔案至S3)
因為這邊要將檔案寫入至S3
因此要先調整Lambda的Execute Role(Lambda的Configuration > Permssion內可找到Role)
直接在Role的Permssion設定中使用"Create inline policy"(內嵌政策)來設定權限即可
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設定
檔案設定(Body > binary)
CORS設定
若要使用瀏覽器發請求
API必須啟用CORS設定
若有像上面範例用一些自訂Header像是filename
Access-Control-Allow-Headers必須要加入自訂的header
使用Cloudwatch來紀錄API Gateway請求
如果要紀錄API Gateway的請求來做Debug或其他用途
就可使用Cloudwatch來做紀錄
這邊要建立一個IAM Role給API Gateway使用
讓它可以有權限寫入Cloudwatch Log
權限可使用預設的Permission "AmazonAPIGatewayPushToCloudWatchLogs"
Step 1 - 選擇服務
服務選擇API Gateway
Step 2 - 設定權限
直接選擇預設的"AmazonAPIGatewayPushToCloudWatchLogs"即可
接著下一步設定好Role名稱之後就可以直接建立Role
Step3 - 在API設定中加入Role ARN
Step 4.1 - 所有API啟用Log
可在整個Stage設定中啟用Cloudwatch Logs功能
並可依照需求決定要紀錄的程度
Step 4.2 - Method個別設定Log
可在Method中覆寫Stage的Log設定
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
這點跟415 - Unsupported Media Type這個錯誤剛好相反
這是API沒將該content-type設定為binary media type
但請求body又送出binary檔案
導致API Gateway直接把body當成json解析出現的錯誤
遇到這個錯誤看是要把API改成binary類型
或是改成client請求不要送binary檔案即可