AWS: 小全端 part 2
有時候覺得事情應該很簡單
卻發現實際上很困難
但最後發現問題與解法相當簡單
自己卻完全想不到時
這大概就是最想放棄的時候
前情提要
我想透過給定 ID 去抓 xkcd 的 API 然後畫成文字圖。也就是用 /xkcd/614
接 https://xkcd.com/614/info.0.json
接著傳給前端。
圖是這樣畫的:
使用者 <=====> AWS Amplify <=====> Amazon API Gateway <=====> AWS Lambda <=====> xkcd API
Lambda 牛刀小試:奇數與偶數
既然 AWS Lambda 就是程式的話,那先寫個判斷奇數與偶數的程式 (HN)也不為過吧。
Okay, let's go.
export const handler = async (event) => {
const check_even_main = (input = 0, target = true) => {
if( isNaN(input) ) {
return false;
}
const check_even_func = (input = 0) => input % 2 === 0;
return check_even_func(input) === target;
};
const input = parseInt(event.input, 10);
const response = {
input,
result: {
is_even: check_even_main(input, true),
is_odd: check_even_main(input, false),
}
};
return response;
};
完成。然後程式這樣傳值:
is_odd_or_even({ input: 0 }); // returns: { "input": 0, "result": { "is_even": true, "is_odd": false } }
Lambda 正事: xkcd API
還不錯。那就開始做正事吧。
/* global fetch */
const get_api = (id = "") => {
if(id =="") {
return "https://xkcd.com/info.0.json";
}
return "https://xkcd.com/" + id + "/info.0.json";
};
const ajax = async(id = "") => {
const default_json = {
"month": "1",
"num": 0,
"link": "",
"year": "1970",
"news": "",
"safe_title": "",
"transcript": "",
"alt": "",
"img": "",
"title": "",
"day": "1"
};
try {
const res = await fetch( get_api(id) );
if( res.status == 404 ) {
return {
...default_json,
message: "No such comic",
};
}
return res.json();
} catch (e) {
console.error(e);
return {
...default_json,
message: "Error occurs",
e,
};
}
};
export const handler = async (event) => {
const id = event.id;
const response = await ajax(id);
return response;
};
fetch_xkcd({ id: "614" }); // { "month": "7", "num": 614, ... }
很滿意的結果。
Amazon API Gateway: endpoints
接下來,既然要呼叫 /xkcd/614
的話,當然就是 GET /xkcd/{id}
,然後事情就簡單啦:
- Create API: => Build REST API => Create REST API and choose Edge-optimized
- Create resource: Create resource name
/xkcd
then/{id}
- Create
GET
andOPTIONS
(to apply CORS) - Lambda integration, done!
把 API 格式開好,叫 Lambda 幫我請 API,事情潮簡單der──
Amazon API Gateway: 除錯之道既阻且長
──並不是。
/xkcd/614
{"month":"5","num":2934,"link":"","year":"2024","news":"","safe_title":"Bloom Filter","transcript":"","alt":"Sometimes, you can tell Bloom filters are the wrong tool for the job, but when they're the right one you can never be sure.","img":"https://imgs.xkcd.com/comics/bloom_filter.png","title":"Bloom Filter","day":"17"}
事實上 API 都在請最新的 API。GET /xkcd/{id}
的 /{id}
彷彿一點用也沒有。
困擾了很久,和 ChatGPT 談過,她建議用 pathParameters
參數,類似這樣:
export const handler = async (event) => {
const id = event.pathParameters.id;
const response = {
statusCode: 200,
body: { id },
};
return response;
};
所以我給前面的 handler 這樣做:
export const handler = async (event) => {
const id = event.pathParameters.id;
const response = await ajax(id);
return response;
};
結果卻回傳 TypeError: Cannot read property 'id' of undefined
Amazon API Gateway: 出乎意料的大逆轉
我花了很久、開了幾個新的來測試,才發現是因為 pathParameters 必須開 Lambda proxy integration 才能用:
it returns: {"errorMessage": "'pathParameters'", "errorType": "KeyError", "requestId": "904c51f2-9b2f-4e94-a475-25f40b95515b", "stackTrace": [" File "/var/task/lambda_function.py", line 4, in lambda_handler\n input_value = event['pathParameters']['input']\n"]} -- What happened?
The error you are encountering indicates that the
pathParameters
key is not present in the event object passed to your Lambda function. This typically happens when the request is not properly configured to pass the path parameter to the Lambda function. Let's review the configuration and make sure everything is set up correctly.
- Check API Gateway Method Integration: Ensure that the method integration is set to use Lambda Proxy integration. This allows the entire request to be passed as-is to the Lambda function.
為什麼。
……正當我這麼想時,又出現 {"message": "Internal server error"} 了:
2024 : Endpoint response body before transformations: {"month":"7","num":614,"link":"","year":"2009","news":"","safe_title":"Woodpecker","transcript":"[[A man with a beret and a woman are standing on a boardwalk, leaning on a handrail.]]\nMan: A woodpecker!\n<<Pop pop pop>>\nWoman: Yup.\n\n[[The woodpecker is banging its head against a tree.]]\nWoman: He hatched about this time last year.\n<<Pop pop pop pop>>\n\n[[The woman walks away. The man is still standing at the handrail.]]\n\nMan: ... woodpecker?\nMan: It's your birthday!\n\nMan: Did you know?\n\nMan: Did... did nobody tell you?\n\n[[The man stands, looking.]]\n\n[[The man walks away.]]\n\n[[There is a tree.]]\n\n[[The man approaches the tree with a present in a box, tied up with ribbon.]]\n\n[[The man sets the present down at the base of the tree and looks up.]]\n\n[[The man walks away.]]\n\n[[The present is sitting at the bottom of the tree.]]\n\n[[The woodpecker looks down at the present.]]\n\n[[The woodpecker sits on the present.]]\n\n[[The woodpecker pu [TRUNCATED] 2024 : Execution failed due to configuration error: Malformed Lambda proxy response 2024 : Gateway response type: DEFAULT_5XX with status code: 502 2024 : Gateway response body: {"message": "Internal server error"}
明明就有回應……問題難道是 Execution failed due to configuration error: Malformed Lambda proxy response
嗎?然後再查了下,因為當你開 Lambda proxy integration 以後,所有請求與回應都必須要用 API Gateway 的格式了。
這一點天理也沒有。
被氣到不想寫前端部份了,小全端到此為止。
結語
Lambda 如果和 Amazon API Gateway 結合會有很多麻煩與眉角。花上整天除錯也不是不可能。
程式啊──自己來幹自己事很簡單,但與其他東西結合就是一團亂。
參考資料
- Serverless Function, FaaS Serverless - AWS Lambda - AWS
- Run a Serverless "Hello, World!" with AWS Lambda
- Build a Serverless Web Application with AWS Lambda, Amazon API Gateway, AWS Amplify, Amazon DynamoDB, and Amazon Cognito
- Day05-概觀(一)Lambda、API Gateway以及Lambda Proxy integration - iT 邦幫忙::一起幫忙解決難題,拯救 IT 人的一天
- node.js - AWS Lambda event.pathParameters is undefined and hence cannot destructure a property - Stack Overflow
- What Causes Malformed Lambda Proxy Response and How to Fix it | HackerNoon
- node.js - AWS lambda api gateway error "Malformed Lambda proxy response" - Stack Overflow
- Set up Lambda proxy integrations in API Gateway - Amazon API Gateway