API GatewayとLambdaのデータ連携学び直しハンズオン

0.はじめに

API Gateway から Lambda関数への接続について、雰囲気で構築していたことをここに正直に告白致します。
なので改めてデータの渡しかたについて学びなおしたいと思います。

0.1.学びなおしたいこと

今回は、以下4点について学びなおしたいと思います。

  • パスパラメータ(URLパス内の変数)の取得方法
  • クエリパラメータ(URLの?以降)の取得方法
  • リクエストボディからのJSONデータの取得方法
  • ヘッダー情報の取得方法

1.基本的な用語

1.1.パス パラメータとは?

  • Webサーバの情報にアクセスするためにURLに値を埋込む(URLの一部)方法
  • 個人的理解:特定サーバへアクセスするためのパスのためのパラメータ
# パスパラメータ 基本の型
https://hogehoge.jp/user/{user_id}

# 実際のURL
https://hogehoge.jp/user/123456

1.2.クエリ パラメータとは?

  • Webサーバーに情報を検索するためにURLに付け加える方法
  • 個人的理解:サーバ内の情報を検索するためのクエリのためのパラメータ
# クエリパラメータ 基本の型
「?(ここからスタート)」+「変数(パラメータの変数)」+「=」+「変数の値(パラメータの値)」

#  例1:「変数:user_id」で「変数の値:12345」
https://hogehoge.jp/?user_id=12345

#  例2:パラメータが複数ある場合は「&」でつなぐ
https://hogehoge.jp/?user_id=12345&name=tetutetu

1.3.使い分けのポイント

比較 パス パラメータ クエリ パラメータ
役割 リソースの特定・識別 フィルタリング・検索・オプション指定
位置 位置URLのパス部分に埋め込む URLの末尾に?以降で追加
必須/任意 通常は必須 通常は任意
複数指定 階層構造 &で区切って複数指定

1.4.リクエストボディとは?

  • HTTPリクエストの本文として送信されるデータ
    主にPOST、PUT、PATCHなどのメソッドでデータを送信する際に使用される。
  • リクエストボディのデータ構造は、そのAPI作成者が設計して管理する。
例:以下のようなアンケートページの場合
  • APIの設計:
    このAPIにはnameとemail、質問の回答としてq1、q2 というフィールドが必要と設計

  • フロントエンドの実装:
    ユーザーが入力したデータを集め、決められた形式(name等)でAPI(https://hogehoge.jp/submit-survey)にPOST

// フロントエンド側のコード(ユーザーがフォームに入力した後)
const userData = {
  name: "tetutetu",  // ユーザーが入力した名前
  email: "hogehoge@hugahuga.com",  // ユーザーが入力したメール
  q1: "はい",  // 質問1の回答
  q2: "いいえ",  // 質問2の回答
};

// このデータをAPIにPOST
fetch('https://hogehoge.jp/submit-survey', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify(userData)  // ここでJSONに変換してリクエストボディとして送信
});
  • バックエンドの実装:
    送られてきたリクエストボディを受け取って処理
# Lambda関数側(バックエンド)
def lambda_handler(event, context):
    # リクエストボディを取得してJSONに変換
    body = json.loads(event.get('body', '{}'))

    # 各フィールドを取り出す
    name = body.get('name')
    email = body.get('email')
    q1_answer = body.get('q1')
    q2_answer = body.get('q2')

1.5.ヘッダー情報とは?

  • HTTPリクエストの本文以外の送信されるデータ

  • 基本的なヘッダーはHTTP標準で決まってるが、カスタムヘッダーとして追加も可能で、その部分に関してはAPI作成者が設計・管理する。

  • 一般的なヘッダーの例:

項番 ヘッダー名 詳細
1 Content-Type リクエストボディの形式(例:application/json)
2 Authorization 認証情報(例:Bearer eyJhbGciOiJ…)
3 Accept クライアントが受け入れられるレスポンス形式
4 User-Agent クライアントの種類(ブラウザ情報など)
  • カスタムヘッダーの例:
項番 ヘッダー名 詳細
1 X-API-Key APIキー
2 X-Request-ID リクエスト識別子
3 X-Custom-Header 独自の情報
  • バックエンドの実装:
    ヘッダー情報の取得方法
def lambda_handler(event, context):
    # ヘッダー情報を取得
    headers = event.get('headers', {})

    # 標準ヘッダー
    content_type = headers.get('Content-Type')
    authorization = headers.get('Authorization')

    # カスタムヘッダー
    api_key = headers.get('X-API-Key')

2.ハンズオン

2.0.前提条件

  • 以下ハンズオンは、AWS CloudShell環境にて実施する

2.1.Lambda用 IAMロール作成

# 作業フォルダ作成
mkdir 20250429_handson && cd 20250429_handson

# 変数設定
LAMBDA_FUNCTION_NAME="submit-survey"
API_NAME="survey-api"
STAGE_NAME="dev"
REGION="us-east-1"
ROLE_NAME="lambda-submit-survey-role"

# 信頼ポリシードキュメントの作成
cat > trust-policy.json << EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF

# IAMロールの作成
aws iam create-role --role-name $ROLE_NAME --assume-role-policy-document file://trust-policy.json

# IAMロールにLambda基本実行ポリシーをアタッチ
aws iam attach-role-policy --role-name $ROLE_NAME --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

# IAMロールARNの取得
ROLE_ARN=$(aws iam get-role --role-name $ROLE_NAME --query 'Role.Arn' --output text)
echo "作成したロールARN: $ROLE_ARN"

2.2.Lambda 作成

2.2.1.Lambda関数作成

# Lambda関数用のフォルダ作成
mkdir lambda_function

# Lambda関数のPythonコードを作成
cat > lambda_function/lambda_function.py << EOF
import json

def lambda_handler(event, context):
    # 受信イベントの全体をログに出力
    print(f"受信イベント: {json.dumps(event)}")

    # --- パスパラメータの取得 ---
    # pathParameters キーが存在するか確認し、辞書として取得
    # 例: /user/{user_id} の場合、event['pathParameters']['user_id'] で取得
    path_parameters = event.get('pathParameters', {})
    print(f"パスパラメータ: {json.dumps(path_parameters)}")

    # --- クエリパラメータの取得 ---
    # queryStringParameters キーが存在するか確認し、辞書として取得
    # 例: /?user_id=123&name=abc の場合、event['queryStringParameters']['user_id'] で取得
    query_parameters = event.get('queryStringParameters', {})
    print(f"クエリパラメータ: {json.dumps(query_parameters)}")

    # --- ヘッダー情報の取得 ---
    # headers キーが存在するか確認し、辞書として取得
    # ヘッダー名は環境によって大文字小文字が異なる場合があるため、取得時は注意
    headers = event.get('headers', {})
    print(f"ヘッダー情報: {json.dumps(headers)}")
    # 特定のヘッダーを取得する例
    content_type = headers.get('Content-Type') # 一般的
    # または headers.get('content-type') のように小文字で試す場合もある
    authorization = headers.get('Authorization')
    custom_header = headers.get('X-Custom-Header') # カスタムヘッダーの例

    print(f"Content-Type: {content_type}")
    print(f"Authorization: {authorization}")
    print(f"X-Custom-Header: {custom_header}")

    # --- リクエストボディの解析 ---
    body = {}
    if event.get('body'):
        try:
            # API Gateway プロキシ統合の場合、bodyは文字列として渡されるためjson.loadsが必要
            body = json.loads(event.get('body'))
            print(f"リクエストボディ: {json.dumps(body)}")

            # 各フィールドを取り出す
            print(f"名前 (ボディ): {body.get('name')}")
            print(f"メール (ボディ): {body.get('email')}")
            print(f"質問1 (ボディ): {body.get('q1')}")
            print(f"質問2 (ボディ): {body.get('q2')}")

        except Exception as e:
            print(f"ボディの解析エラー: {e}")
            # エラーレスポンスを返す例
            return {
                'statusCode': 400,
                'headers': { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' },
                'body': json.dumps({'message': 'リクエストボディの形式が不正です'})
            }

    # 成功レスポンスを返す
    return {
        'statusCode': 200,
        'headers': {
            'Content-Type': 'application/json',
            'Access-Control-Allow-Origin': '*'  # CORSを有効化
        },
        'body': json.dumps({
            'message': 'データを受信しました',
            'received_data': {
                'pathParameters': path_parameters,
                'queryStringParameters': query_parameters,
                'headers': headers,
                'body': body
            }
        }, ensure_ascii=False)
    }
EOF

2.2.2.Lambda関数のzip作成

cd lambda_function
zip -r ../lambda_function.zip .
cd ..

2.2.3.Lambdaのデプロイ

# Lambdaデプロイコマンド
aws lambda create-function \
  --function-name $LAMBDA_FUNCTION_NAME \
  --runtime python3.9 \
  --handler lambda_function.lambda_handler \
  --role $ROLE_ARN \
  --zip-file fileb://lambda_function.zip

# LambdaのARN取得
LAMBDA_ARN=$(aws lambda get-function --function-name $LAMBDA_FUNCTION_NAME --query 'Configuration.FunctionArn' --output text)
echo "作成したLambda関数ARN: $LAMBDA_ARN"

2.3.API Gatewayの作成

  • APIGatewayの構成
項番 項目 設定値 説明
1 APIの種類 REST API 構築するAPIのタイプ
2 リソース /submit-survey クライアントからのURLパス
3 メソッド POST /submit-survey リソースに対して許可された操作
4 統合タイプ AWS_PROXY リクエストを Lambda 関数に直接転送する方式(プロキシ統合)
5 統合先 $LAMBDA_FUNCTION_NAME リクエスト処理を実行する Lambda 関数名
6 パラメータ・ボディ・ヘッダーの転送 自動 プロキシ統合によりevent に全て含まれる
# REST APIの作成
API_ID=$(aws apigateway create-rest-api \
  --name $API_NAME \
  --endpoint-configuration types=REGIONAL \
  --query 'id' --output text)
echo "作成したAPI ID: $API_ID"

# ルートリソースIDの取得
ROOT_RESOURCE_ID=$(aws apigateway get-resources \
  --rest-api-id $API_ID \
  --query 'items[0].id' --output text)
echo "ルートリソースID: $ROOT_RESOURCE_ID"

# /submit-survey リソースの作成
RESOURCE_ID=$(aws apigateway create-resource \
  --rest-api-id $API_ID \
  --parent-id $ROOT_RESOURCE_ID \
  --path-part "submit-survey" \
  --query 'id' --output text)
echo "作成したリソースID: $RESOURCE_ID"

# POSTメソッドの作成
aws apigateway put-method \
  --rest-api-id $API_ID \
  --resource-id $RESOURCE_ID \
  --http-method POST \
  --authorization-type NONE

# Lambda関数との統合
aws apigateway put-integration \
  --rest-api-id $API_ID \
  --resource-id $RESOURCE_ID \
  --http-method POST \
  --type AWS_PROXY \
  --integration-http-method POST \
  --uri arn:aws:apigateway:$REGION:lambda:path/2015-03-31/functions/$LAMBDA_ARN/invocations

2.4.Lambdaにリソースベースポリシーを付与

# Lambda関数にAPIゲートウェイからの呼び出し権限を付与
aws lambda add-permission \
  --function-name $LAMBDA_FUNCTION_NAME \
  --statement-id apigateway-permission \
  --action lambda:InvokeFunction \
  --principal apigateway.amazonaws.com \
  --source-arn "arn:aws:execute-api:$REGION:$(aws sts get-caller-identity --query 'Account' --output text):$API_ID/*"

2.5.APIGateway デプロイ

# APIのデプロイ
aws apigateway create-deployment \
  --rest-api-id $API_ID \
  --stage-name $STAGE_NAME

# API URLの表示
API_URL="https://$API_ID.execute-api.$REGION.amazonaws.com/$STAGE_NAME/submit-survey"
echo "API URL: $API_URL"

2.6.挙動確認

2.6.1.APIGatewayにコマンド実行

# 以下コマンドで URLを作成できます
echo "curl -X POST -H \"Content-Type: application/json\" -d '{\"name\":\"てつてつ\",\"email\":\"test@example.com\",\"q1\":\"はい\",\"q2\":\"いいえ\"}' $API_URL"

# レスポンスで出力された curl コマンドを実行
# 以下 例URL
curl -X POST -H "Content-Type: application/json" -d '{"name":"てつてつ","email":"test@example.com","q1":"はい","q2":"いいえ"}' https://${API_ID}.execute-api.us-east-1.amazonaws.com/dev/submit-survey

# 出力レスポンス例(※可視性を重視し、実際の出力から改行しています)
{
  "message": "データを受信しました",
  "received_data": {
    "pathParameters": null,
    "queryStringParameters": null,
    "headers": {
      "accept": "*/*",
      "content-type": "application/json",
      "Host": "{$API_ID}.execute-api.us-east-1.amazonaws.com",
      "User-Agent": "curl/8.5.0",
      "X-Amzn-Trace-Id": "Root=1-68106501-6dd9506f01c1329f40e329b2",
      "X-Forwarded-For": "XX.YY.ZZ.AA",
      "X-Forwarded-Port": "443",
      "X-Forwarded-Proto": "https"
    },
    "body": {
      "name": "てつてつ",
      "email": "test@example.com",
      "q1": "はい",
      "q2": "いいえ"}
    }
}

2.6.2.Lambdaのログの確認

  • submit-survey関数の CloudWatchLogs画面で以下のログが取得できていることが確認できる
  • 受信イベント:eventすべてのログの取得
  • パスパラメータ:今回は存在していないためnull
  • クエリパラメータ:今回は存在していないためnull
  • ヘッダー情報:一般的なヘッダー内容
  • 特定のヘッダー情報:今回は3つcontent-Type,Authorization,X-Custom-Header取得
  • リクエストボディ:※Unicodeエスケープシーケンス
  • リクエストボディ詳細: 名前,メール,質問を取得

file

3.おわりに

3.1.得られた知見

  • API Gatewayプロキシ統合では、リクエストの全てが event という一つの辞書オブジェクトとしてLambdaに渡される
  • パスパラメータは event[‘pathParameters’]、クエリパラメータは event[‘queryStringParameters’]、ヘッダー情報はevent[‘headers’] に含まれる
  • json.dumps() を使ってログに出力
  • リクエストボディは文字列として渡されるため json.loads() が必要

3.2.今後の課題

  • パスパラメータをAPI Gatewayで設定し、それを利用する具体的なユースケースを実装。
  • APIキーや認証・認可(Lambdaオーソライザーなど)の仕組みと、その際のヘッダー情報の利用。
Last modified: 2025-04-29

Author