AWS: 小全端 part 2

有時候覺得事情應該很簡單
卻發現實際上很困難
但最後發現問題與解法相當簡單
自己卻完全想不到時
這大概就是最想放棄的時候

前情提要

我想透過給定 ID 去抓 xkcd 的 API 然後畫成文字圖。也就是用 /xkcd/614https://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},然後事情就簡單啦:

  1. Create API: => Build REST API => Create REST API and choose Edge-optimized
  2. Create resource: Create resource name /xkcd then /{id}
  3. Create GET and OPTIONS (to apply CORS)
  4. 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.

  1. 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 結合會有很多麻煩與眉角。花上整天除錯也不是不可能。

程式啊──自己來幹自己事很簡單,但與其他東西結合就是一團亂。

參考資料