0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AWS初心者のLambda開発

Posted at

本開発の経緯

本開発の背景はAWSを使って何かを作ってみたかったからになります。CLFを取得しSAAを勉強中ですが、現職では機会がなく、だったら自分でやってみようと思ったのと同期とも「やってみようぜ!」という話になり本開発を実施しました。システムとしては非常にシンプルでAPI GatewayとLambdaを使用してGoogleMapsAPI呼び出し、おすすめの場所などをユーザーへ返却するサービスを開発しました。

システム構成図

下記が構成図になります。非常にシンプルな構成となっています。
・想定動作

Users → Internet → API Gateway → Lambda → GoogleMapsAPI → Lambda→API → Gateway → Users

・AWS CLIでのコマンド実行を想定。
・リクエストはJSON形式を想定し、返却結果もJSON形式で受信。
・API GatewayにはAPIキーを設定し、セキュリティを担保。
名称未設定ファイル.drawio.png

Lambdaへの実装コード

同期と分担して作成しました。新人研修以来のPythonで難しかったです…
私はクライアントからのパラメータを受信するapi_param_recvを担当しました。
★作成したコード★

import json
import requests

def api_param_recv(event: dict, context: object) -> dict:
    event_param_recv = {key: str(value) for key, value in event.items()}

    for value in event_param_recv.values():
        if not isinstance(value, str):
            return send_response(1,{})
        
    # Step 2: Validate parameters
    if not param_validation(event_param_recv):
        return send_response("Invalid parameters", None)

    # Step 3: Call external API
    api_result, error_message, api_response = call_find_place_api(event_param_recv)

    if(error_message is not None):
        send_data = error_message
    else:
        send_data = api_response 

    # Step 4: Send response
    return send_response(api_result, send_data)


def param_validation(event_str_params: dict) -> bool:

    # Initialize return values
    result = 1  # Default to failure
    error_message = []  # List to store error reasons
    validated_params = {}  # Placeholder for validated parameters

    # Validation rules
    required_fields = {
        "queryText": str,
    }

    optional_fields = {
        "placeType": str,
        "latitude": float,
        "longitude": float,
        "radius": float,
        "minRate": float,
        "strictType": bool,
        "priceLevel": list,
        "searchFields": list,
    }

    coordinate_fields = ["latitude", "longitude", "radius"]

    # Check required fields
    for field, field_type in required_fields.items():
        if field not in event_str_params or not isinstance(event_str_params[field], field_type):
            error_message.append(f"{field}: Missing or invalid type")

    # Check optional fields
    for field, field_type in optional_fields.items():
        if field in event_str_params:
            if field == "priceLevel" or field == "searchFields":
                if not isinstance(event_str_params[field], list):
                    error_message.append(f"{field}: Must be a list")
                else:
                    if field == "priceLevel" and not all(isinstance(i, int) and i in [0, 1, 2, 3, 4] for i in event_str_params[field]):
                        error_message.append(f"{field}: Must contain values 0, 1, 2, 3, or 4")
            elif field == "minRate":
                if not (0 <= event_str_params[field] <= 5):
                    error_message.append(f"{field}: Must be between 0 and 5")
            elif not isinstance(event_str_params[field], field_type):
                error_message.append(f"{field}: Invalid type")

    # Check coordinate dependencies
    coordinates_present = [field for field in coordinate_fields if field in event_str_params]
    if 0 < len(coordinates_present) < 3:
        error_message.append("latitude, longitude, and radius must be specified together")

    # If no validation errors, set validated_params
    if not error_message:  # If no errors occurred
        result = 0  # Validation successful
        validated_params = event_str_params  # Assume all parameters are valid for now
    else:
        validated_params = None  # Set validated_params to None on failure

    return result, (error_message if error_message else None), validated_params

def call_find_place_api(api_params: dict[str]):

    try:
        # Find Place APIを呼び出す
        response = requests.get("URL", params=api_params)
        
        # レスポンスのステータスコードを確認
        if response.status_code == 200:
            data = response.json()
            result = 0
            error_message = None
            find_place_info = data  # 必要に応じて data の中身を加工してください
        else:
            result = 1
            error_message = f"HTTPエラー: {response.status_code}"
            find_place_info = None
    except Exception as e:
        result = 1
        error_message = str(e)
        find_place_info = None

    return result, error_message, find_place_info

def send_response(result: int, find_place_info: dict):

    # Determine the status code
    if result == 0:
        status = 200
    else:
        status = 400

    # Convert result and find_place_info to a string
    result_message = json.dumps({"result": result, "info": find_place_info})

    # Create the response as a JSON object
    response = {"status": status, "result": result_message}

    # Simulate sending the response (replace this with actual response logic in a real application)
    print("Response sent to client:", response)

    return response

設定手順

API Gateway設定手順

1.[ APIを作成 ]をクリックする。
 右上の[ APIを作成 ]を押下する。
image.png

2.APIタイプを選択する。
 今回はREST APIを選択
image.png

3.[ APIを作成 ]をクリックする。
 下記に設定し、[ APIを作成 ]をクリックする。
 ・API:Google-Maps-APIGW(任意)
 ・説明-オプション:API Gatewayの説明などを記載
 ・APIエンドポイントタイプ:リージョン
image.png

4.[ メソッドを作成 ]をクリックする。
image.png
 クリック後、メソッド作成画面に遷移するので下記設定を行い、[ メソッド作成 ]をクリックする。
 ・メソッドタイプ:GET
 ・統合タイプ:Lambda関数
 ・Lambdaプロキシ統合:ON
 ・Lambda関数:リージョン、統合したいLambdaのARN
image.png

5.[ APIをデプロイ ]をクリックする。
 クリック後6の画面に遷移する。
image.png

6.ステージを選択し、ステージ名を記載後に[ デプロイ ]をクリックする。
 下記設定をして
 ・ステージ:新しいステージを選択。(ステージ名は任意)
 ・デプロイメントの説明:デプロイメントの説明を記載する。(任意)
image.png

APIキー設定手順

1.APIキーページに遷移し、APIキーの作成をクリックする。
image.png
image.png

2.以下を入力・選択し、[ 保存 ]をクリックする。
 下記を入力し、保存するとAPIキーが生成される。
 ・名前:Google-Maps-APIkey(任意)
 ・説明-オプション:APIキーの説明を記載。
 ・APIキー:自動生成
image.png

3.使用量プランのページに遷移し、以下を入力・選択し、[ 使用量プランを作成 ]をクリックする。
image.png
image.png

4.以下を入力・選択し、[ 使用量プランを作成 ]をクリックする。
 ・名前:Google-Maps-APIplan(任意)
 ・説明-オプション:使用量プランの説明を記載。
 ・レート:任意の数値
 ・バースト:任意の数値
 ・リクエスト:任意の数値と期間
image.png

5.APIキー画面に戻り、関連付けられた使用量プランを選択し[ 使用量プランに追加 ]をクリックする。
image.png
image.png

6.作成した使用量プランにキーを追加し、結びつけ[ 保存 ]をクリックする。
image.png

7.再度、使用量プラン詳細画面に遷移し、関連付けられたステージを選択し、[ ステージを追加 ]をクリックする。
image.png

8.関連付けたいAPI、ステージを設定し、[ 使用量プランに追加 ]をクリックする。
image.png

9.リソース画面に遷移し、メソッドリクエストタブの[ 編集 ]をクリックし、再デプロイ。
image.png
image.png

10.メソッドリクエストを編集画面に遷移し、[ APIキーが必須です ]にチェックし、[ 保存 ]をクリックする。
image.png

Lambda設定手順

1.マネジメントコンソール画面より、Lambdaを選択する。
image.png

2.Lambdaの関数ページに遷移し、右上の[ 関数を作成 ]をクリックする。
image.png
image.png

3.以下を入力・選択し、[ 関数の作成 ]をクリックする。
 ・一から作成
 ・関数名:Google-Maps-API-Lambda(任意)
 ・ランタイム:python3.13
 ・アーキテクチャ:x86_64
 ・実行ロール:基本的な Lambda アクセス権限で新しいロールを作成
image.png

4.[ コード ] をクリックし、赤枠内に作成したじコードをコピー&ペーストする。
image.png

5.コピー&ペーストされたことを確認し、左のペインにある[ Deploy ]をクリックする。
image.png

6.レイヤーにPython_pakcageを追加し、[ 追加 ]をクリックする。
image.png

API GatewayとLambdaの統合手順

1.トリガーを追加をクリックする。
image.png

2.ソースを選択からAPI Gatewayを選択し[ 追加 ]をクリックする。
image.png

実際に動かしてみた

コマンド

★コマンド例

curl -X GET "API GatewayのURL" --header "x-api-key:xxxxxxxxxxxxxxxxxxx" "Content-Type: application/json" --data-raw "{ \"xxxxxx\": \"xxxxxx\" }" -o response.json.txt

結果

{"message": "Internal server error"}
ログを確認してみたころ、200と帰ってきているので問題なさそうだが...

Lambdaのログ
timestamp,message
1740330499935,"START RequestId: e968ccbc-3ba7-4628-8358-499998e8d11e Version: $LATEST
"
1740330500194,"Response sent to client: {'status': 200, 'result': '{""result"": 0, ""info"": {""candidates"": [], ""error_message"": ""You must use an API key to authenticate each request to Google Maps Platform APIs. For additional information, please refer to http://g.co/dev/maps-no-account"", ""status"": ""REQUEST_DENIED""}}'}
"
1740330500196,"END RequestId: e968ccbc-3ba7-4628-8358-499998e8d11e
"
1740330500196,"REPORT RequestId: e968ccbc-3ba7-4628-8358-499998e8d11e	Duration: 260.67 ms	Billed Duration: 261 ms	Memory Size: 128 MB	Max Memory Used: 58 MB	
"

Postmanで確認してみたら502エラーだったので、API Gateway側の問題らしいが、対処方法がわからず...
問題原因がAPI Gatewayだとできたので、再度調査してまた具体的な原因、対処方法を記事にします。
image.png

最後に

今回は残念ながらエラーでしたが、原因が恐らくAPI Gateway側にあるということが分かったので、再度調査し記事にします。
ただ、Lambdaの設定、API Gatewayも設定方法をアウトプットする良い機会になりました。
やっぱり、アウトプットって大事ですね...
初めての記事だったので、読みづらい所もあるとは思いますが、最後まで見ていただきありがとうございます。
また近いうちに対処記事を上げますので、そちらもぜひ見ていただければと思います。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?