AWS Lambda Powertools Python入門 第 5 回 EventHandler Utility - REST API 編

2022-09-02
デベロッパーのためのクラウド活用方法

Author : 福井 厚

皆さん、こんにちは。AWSソリューションアーキテクトの福井です。本連載は、AWS Lambda Powertools Pythonの提供する様々なユーティリティーについて、その利用方法を紹介するシリーズですが、この度、Lambda Powertools for TypeScript も一般利用可能 (GA) となりました。普段、Lambda 関数を TypeScript で記述されている方々にとっては嬉しいお知らせではないでしょうか。この連載を参考にしつつ、TypeScript 版もぜひお試しください。

さて、第 4 回 では、AWS Lambda Powertools Python のMetricsユーティリティーの使い方についてご紹介しました。今回は Core Utilities の中の Event Handler についてご紹介します。なお、本シリーズでは、AWS Lambda Powertools Python 1.25.1 を対象としています。

この連載記事のその他の記事はこちら

選択
  • 選択
  • AWS Lambda Powertools Python 入門 第 1 回
  • 第 2 回 ~Tracer Utility
  • 第 3 回 ~Logger Utility
  • 第 4 回 ~Metrics Utility
  • 第 5 回 ~EventHandler Utility - REST API 編
  • 第 6 回 EventHandler Utility - GraphQL 編

Event Handler (Core Utilities)

Lambda Powertools Python の Event Handler は、REST API と GraphQL API の両方が提供されています。今回は REST API について紹介し、GraphQL API は次回紹介する予定です。


REST API

Amazon API Gateway の REST API、HTTP API、および Application Load Balancer (ALB) のためのイベントハンドラを提供します。REST API イベントハンドラの主要な機能は以下の通りです。

  • API Gateway REST / HTTP API と ALB に対するボイラープレートを減らす軽量なルーティング
  • CORS、バイナリーと Gzip 圧縮、Decimal の JSON エンコーディング、独自の JSON シリアライザをサポート
  • 自己記述型イベントスキーマのための Event Source Data Classes ユーティリティーとの統合

利用方法

AWS Lambda Powertools Python を Lambda 関数に組み込む方法については、第 1 回 の記事を参照ください。既存の Amazon API Gateway でプロキシ統合を設定するか、ALB で Lambda 関数を呼び出すように設定しておく必要があります。

AWS Serverless Application Model (SAM) の例です。

AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: >
  restapi-eventhandler-demo

Globals:
  Api:
    TracingEnabled: true
    Cors:
      AllowOrigin: "'*'"
      AllowHeaders: "'Content-Type,Authorization,X-Amz-Date'"
      MaxAge: "'300'"
    BinaryMediaTypes:
      - "*~1*"
    EndpointConfiguration:
      Type: REGIONAL
  Function:
    Timeout: 5
    Runtime: python3.9
    Tracing: Active
    Environment:
      Variables:
        LOG_LEVEL: INFO
        POWERTOOLS_LOGGER_SAMPLE_RATE: 0.1
        POWERTOOLS_LOGGER_LOG_EVENT: true
        POWERTOOLS_SERVICE_NAME: restapi-eventhandler-demo

Resources:
  RestAPiFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: ./src
      Handler: app.lambda_handler
      Runtime: python3.9
      Architectures:
        - x86_64
      Layers:
        - !Sub arn:aws:lambda:${AWS::Region}:017000801446:layer:AWSLambdaPowertoolsPython:11
      Events:
        AnyApiEvent:
          Type: Api
          Properties:
            Path: /{proxy+}
            Method: ANY
            

イベントリゾルバ

関数のデコレーターをセットしてリクエストの path や HTTP のメソッドをハンドルする前に、リゾルバを初期化する必要があります。リゾルバはリクエストを解決するために、1 つ以上のルーティング処理を含みます。また型付けされたプロパティーを通じて現在のイベントにアクセスできるようにします。

リゾルバとして、APIGatewayRestResolver、APIGatewayHttpResolver、ALBResolver を提供しています。

Amazon API Gateway REST API

Lambda 関数のフロントとして、Amazon API Gateway の REST API を利用する場合は、APIGatewayRestResolver を利用することができます。

例えば、Amazon API Gateway の REST API から GET リクエストの path で /products をハンドルする方法は以下の通りです。

クリックすると拡大します

API Gateway の公開している URL に /products のパスをつけて呼び出すと Lambda 関数は API Gateway に対して以下のレスポンスを返します。

{
    "statusCode": 200,
    "headers": {
        "Content-Type": "application/json"
    },
    "body": "{\"products\":[{\"id\":\"0001\",\"name\":\"M01\",\"price\":1000},
    {\"id\":\"0002\",\"name\":\"M02\",\"price\":2000},
    {\"id\":\"0003\",\"name\":\"M03\",\"price\":1500}]}",
    "isBase64Encoded": false
}

このレスポンスを受け取った Amazon API Gateway は、Response の StatusCode として 200 を返し、以下の JSON メッセージをクライアント側に返します。

{
    "products": [
        {
            "id": "0001",
            "name": "M01",
            "price": 1000
        },
        {
            "id": "0002",
            "name": "M02",
            "price": 2000
        },
        {
            "id": "0003",
            "name": "M03",
            "price": 1500
        }
    ]
}

このように、ルートに対するメソッドを定義し、辞書型のオブジェクトをリターンするだけのシンプルな実装で、クライアント側にその値を JSON として返すことができます。

Amazon API Gateway HTTP API

Amazon API Gateway の HTTP API を利用する場合は以下のようなコードになります。違いは、APIGatewayRestResolver の代わりに APIGatewayHttpResolver を利用するところです。

Application Load Balancer

同様に、ALB のロードバランサをイベントソースとして Lambda 関数を実行する場合は、APIGatewayRestResolver の代わりに、ALBResolver を利用します。


動的ルーティング

動的な URL パスを構成するために、/products/<product_id> の形式を利用することができます。<product_id> は、実行時に解決されます。

個々の動的ルーティングは関数のシグネチャの一部である必要があります。これによって動的ルーティングにマッチした時に、キーワード引数を利用して関数が呼び出されます。

この Lambda 関数をデプロイして、API Gateway の URL に /products/0001 のパスをつけて呼び出した結果は以下の通りです。

{"id":"0001","name":"M01","price":1000}

同様に /products/0004 のパスで実行すると以下のような結果がクライアント側に返ります。

{"message":"no match!"}

動的パスはネストすることも可能です。例えば、/products/<product_id>/<product_status> のような呼び出しもできます。

すべてのルートを補足する

正規表現を利用してリクエストに含まれる任意の数のパスを取り扱うことができます。例えば、“.+” を指定するとすべてのパスを補足することが可能です。ただし、すべてのルートを補足することは、できるだけ控えて可能な限り明示的にルートを指定されることをお勧めします。


HTTP メソッド

Lambda 関数でハンドルする HTTP メソッドを指定するために名前付きデコレーター (app.get など) を利用することができます。指定可能なメソッドは、get, post, put, patch, delete, option です。

例えば、post メソッドをハンドルするには、以下のようなコードを記述します。

もし 1 つの関数で複数の HTTP メソッドを受けたい場合は、route メソッドを利用して HTTP メソッドのリストを送ります。

一般的には、HTTP メソッドごとに個別の関数を定義するほうが望ましいです。なぜなら関数で実装する機能は、どの HTTP メソッドを利用するかに依存するからです。


リクエストの詳細へのアクセス

Event Handler は、Event Source Data Classes Utility と統合されており、このユーティリティーは個々のリゾルバのリクエストの詳細と app.current_event の便利なメソッドを提供しています。

すべてのサンプルソースに app.resolve(event, context) のコードが記述されています。これによって Event Handler がリクエストを解決することができ、app.lambda_contextapp.current_event のようなデータを取得することができます。

クエリ文字列とペイロード

app.current_event プロパティ内の query_string_parameters を通じて、すべての利用可能なクエリ文字列にアクセスすることができます。または特定のクエリ文字列を get_query_string_value メソッドで取得することができます。

body プロパティを利用すると生のペイロードにアクセスすることができます。また、Body の 値が JSON 文字列の場合は、 上記の HTTP メソッドの app.post のサンプルコードのように、json_body プロパティを利用して素早くデシリアイズすることができます。

headers の取得

クエリ文字列と同様に、app.current_event.headers を通じて headers の値をディクショナリとして取得できます。また get_header_value を利用して特定の名前を指定して値を取得することができます。

実行結果は以下の通りです。

{"trace_id":"Root=1-62e8614b-5dc882a310b97e464ab07ee4"}

not found ルートのハンドリング

デフォルトでは、すべてのルートにマッチしない場合は 404 ({"statusCode":404,"message":"Not found"}) を返します。not_found デコレーターを使用してこの振る舞いを上書きして、カスタムのレスポンスを返すことができます。

このコードをデプロイして存在しないパスを指定して呼び出すと API Gateway に対して以下のレスポンスを返します。

{
    "statusCode": 404,
    "headers": {
        "Content-Type": "application/json"
    },
    "body": "{\"StatusCode\": 404, \"error_message\": \"\u898b\u3064\u304b\u308a\u307e\u305b\u3093\"}",
    "isBase64Encoded": false
}

このレスポンスを受けて API Gateway はクライアントに StatusCode 400 と共に以下の結果が返されます。

{"StatusCode": 404, "error_message": "見つかりません"}

例外のハンドリング

あらゆる Python の例外に対して exception_handler デコレーターを使用することができます。例えば、バリデーションエラーのようなルート以外の一般的な例外をハンドルすることができます。

この Lambda 関数に対して、/products/1 のパスを指定した場合クライアントに {"quantity":1} のレスポンスが返りますが、数字以外の値をパラメータとしてパスに指定すると ValueError が発生します。例えば、/products/a を指定すると StatusCode 400 と共に以下のメッセージがクライアント側に返されます。

Invalid request parameters.

HTTP エラーを発生させる

ServiceError 例外を使用することで、あらゆる HTTP エラーを発生させクライアントに戻すことができます。これによって Lambda 関数が失敗することなく、エラーを通知するための正しい HTTP レスポンスをクライアントに返すことができます。

Tips:もしカスタムヘッダを送信したい場合は、代わりに Response クラスを利用します。

HTTP 400、401、404、500 など、良く利用されるエラーは事前定義エラーとして提供されています。

この Lambda 関数に対して、例えば /unauthorized-error パスを指定して実行すると StatusCode 401 と共に以下のメッセージがクライアント側に返ります。

{"statusCode":401,"message":"Unauthorized"}

このように簡単に HTTP エラーを生成して返すことができます。


CORS

APIGatewayRestResolver のコンストラクタの CORSConfig クラスを利用した cors パラメータを通じて、CORS の設定を行うことができます。これによってパスに一致した関数が実行される時に、常にCORSヘッダーをレスポンスの一部として返すことを保証します。

Tips: cors=False とパラメータを指定することで、オプションで CORS を無効にすることができます。

この Lambda 関数に対して /products のパスで API Gateway を呼び出すと、次のレスポンスを API Gateway に返します。

{
    "statusCode": 200,
    "headers": {
        "Content-Type": "application/json",
        "Access-Control-Allow-Origin": "*",
        "Access-Control-Allow-Headers": "Authorization,Content-Type,X-Amz-Date,X-Amz-Security-Token,X-Api-Key",
        "Access-Control-Max-Age": "300"
    },
    "body": "{\"products\":[{\"id\":\"0001\",\"name\":\"M01\",\"price\":1000},{\"id\":\"0002\",\"name\":\"M02\",\"price\":2000},{\"id\":\"0003\",\"name\":\"M03\",\"price\":1500}]}",
    "isBase64Encoded": false
}

API Gateway はこのレスポンスを以下のレスポインスヘッダへ変換してクライアントに返します。

{
  "Access-Control-Allow-Headers": [
    "Authorization,Content-Type,X-Amz-Date,X-Amz-Security-Token,X-Api-Key"
  ],
  "Access-Control-Allow-Origin": ["*"],
  "Access-Control-Max-Age": ["300"],
  "Content-Type": ["application/json"],
  "X-Amzn-Trace-Id": ["Root=1-62ef9c7b-9750387727c0f180c8db7007;Sampled=1"]
}

まとめ

いかがでしたでしょうか。今回は、AWS Lambda Powertools Python の EventHandler ユーティリティーの REST API について紹介しました。

Event Handler Utility を利用することで Amazon API Gateway や ALB からの Lambda 関数へのイベントのハンドルを容易にし、その実装も容易になることが理解できたかと思います。Amazon API Gateway の  Lambda プロキシ統合や ALB と Lambda 関数の統合を実装する際には、この機能を組み込むことをお勧めします。次回は EventHandler の GraphQL API について紹介します。次回もお楽しみに。

この連載記事のその他の記事はこちら

選択
  • 選択
  • AWS Lambda Powertools Python 入門 第 1 回
  • 第 2 回 ~Tracer Utility
  • 第 3 回 ~Logger Utility
  • 第 4 回 ~Metrics Utility
  • 第 5 回 ~EventHandler Utility - REST API 編
  • 第 6 回 EventHandler Utility - GraphQL 編

builders.flash メールメンバーへ登録することで
AWS のベストプラクティスを毎月無料でお試しいただけます


筆者プロフィール

福井 厚
アマゾン ウェブ サービス ジャパン合同会社
シニアソリューションアーキテクト
Developerスペシャリスト - DevAx

2015 年からアマゾンウェブサービスジャパンでソリューションアーキテクトとして活動。最近は、Developer スペシャリスト SA として日々開発者の方にクラウドネイティブなモダンアプリケーション開発の技術支援を行なっています。

AWS を無料でお試しいただけます

AWS 無料利用枠の詳細はこちら ≫
5 ステップでアカウント作成できます
無料サインアップ ≫
ご不明な点がおありですか?
日本担当チームへ相談する