API Gateway + REST + Lambda 非プロキシ(カスタム)統合でステータス200以外返ってこない

今回は、API Gateway + REST + Lambda非プロキシ(カスタム)統合で200以外返ってこない事象に悩んだので、 その内容についてまとめたいと思います。
2022.10.23

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

データアナリティクス事業本部ビッグデータチームのyosh-kです。
今回は、API Gateway + REST + Lambda非プロキシ(カスタム)統合で200以外返ってこない事象に悩んだので、 その内容についてまとめたいと思います。

上手くいかない点

API GatewayのプロトコルをREST、統合タイプにLambdaを選択し、500エラーとなるLambda関数を呼び出すように設定しましたが、200以外のレスポンスが返却されない状態となっています。

import json


class LambdaException(Exception):
    def __init__(self, status_code: int, error_msg: str):
        self.status_code = status_code
        self.error_msg = error_msg

    def __str__(self):
        obj = {
           'Status': self.status_code,
            'ErrorReason': self.error_msg
        }
        return json.dumps(obj)


class BadRequestException(LambdaException):
    def __init__(self, error_msg: str):
        super().__init__(404, error_msg)


class InternalServerErrorException(LambdaException):
    def __init__(self, error_msg: str):
        super().__init__(500, error_msg)


def lambda_handler(event, context):
    # TODO implement
    try:
     # ERROR!
        hoge
        print('test')
        return {
            'Status' : 200, 
            'body'       : json.dumps({'Status' : '200', 'ErrorReason' : 'None'})
        }
    except Exception as e:
        raise InternalServerErrorException(str(e))

設定内容

実行結果

Sun Oct 23 01:12:56 UTC 2022 : Method response body after transformations: {rorMessage": "{\"Status\": 500, \"ErrorReason\": \"name 'hoge' is not defined\"}", "errorType": "InternalServerErrorException", "requestId": "f562bb8d-c0a5-4f09-82b2-e8eb85adafc", "stackTrace": ["  File \"/var/task/lambda_function.py\", line 37, in lambda_handler\n    raise InternalServerErrorException(str(e))\n"]}
Sun Oct 23 01:12:56 UTC 2022 : Method response headers: {X-Amzn-Trace-Id=Root=1-6357e7a6ce15e89d;Sampled=0, Content-Type=application/json}
Sun Oct 23 01:12:56 UTC 2022 : Successfully completed execution
Sun Oct 23 01:12:56 UTC 2022 : Method completed with status: 200

それぞれの設定の意味を確認しながら修正していたいと思います。

Lambda統合

API GatewayでLambdaを使用する場合、「Lambda プロキシ統合」か「Lambda非プロキシ(カスタム)統合」を選択できます。(以下、「Lambdaカスタム統合」に省略)
Lamdaプロキシ統合は、Lamda上から決められた形式で返却することにより、カスタム統合で必要な統合レスポンスを省略でき、より簡単にセットアップできる仕組みのようです。今回は検証対象外のため、別の機会に検証してみます。

API Gateway で Lambda プロキシ統合を設定する

Lambdaカスタム統合とは、プロキシ統合のセットアップに加えて、受信リクエストデータがどのように統合リクエストにマッピングされるか、統合レスポンスデータの結果がメソッドレスポンスにどのようにマッピングされるかを指定する方式です。

API Gateway で Lambda 統合を設定する

Lambdaカスタム統合の場合は、統合リクエストの画面から、統合タイプ「Lambda関数」を選択します。
Lambdaプロキシ統合の場合、上記設定+「Lambdaプロキシ統合の使用」にチェックを入れます。

統合レスポンス

Lambdaからreturnされるレスポンス形式と、メソッドレスポンスの形式が異なる場合、結果をそのまま渡すか、データをメソッドレスポンスに合わせて変換することができる設定です。 注意点としては、Lamabdaからreturnされるレスポンス形式と一致する正規表現を定義する必要があります。この形式が一致しない場合、レスポンスはデフォルトのレスポンスが使用されていしまいます。

API Gateway で統合レスポンスを設定する

メソッドレスポンス

統合レスポンスデータとマッピングでき、マッピングに従ってAPI Gatewayのレスポンスデータをreturnすることができる設定です。このマッピングを行うことで、必要な値のみのreturnや静的な値のreturnなどを行うことができます。

API Gateway のメソッドレスポンスをセットアップする

修正作業

Lambdaのレスポンスとしては、以下のようにretunされます。

{
  "errorMessage": "{\"Status\": 500, \"ErrorReason\": \"name 'hoge' is not defined\"}",
  "errorType": "InternalServerErrorException",
  "requestId": "b6185075-4cbe-4493-afd5-e1500c81af6c",
  "stackTrace": [
    "  File \"/var/task/lambda_function.py\", line 37, in lambda_handler\n    raise InternalServerErrorException(str(e))\n"
  ]
}

以下記事にもありましたが、Lambdaカスタム統合の場合、統合レスポンス、メソッドレスポンスを正しくデータマッピングしないとデフォルトの200ステータスコードのマッピングが引き継がれてしまいます。 なので、統合レスポンスが正しくマッピングされるように正規表現を修正します。

HTTP カスタム統合の API Gateway でバックエンドのステータスコードと異なるコードが返ってくるのはなぜでしょうか

任意の文字列を表す正規表現「.*」でStatusを囲むことで、マッピングを設定します。マッピングテンプレートに関しては、errorMessageの値のみを抽出したいので、input変数でerrorMessageを指定しています。input変数に関しては、以下記事をご参照ください。

項目 修正内容
Lambda エラーの正規表現 .*"Status": 500,.*
マッピングテンプレート $input.path('$.errorMessage')

API Gateway マッピングテンプレートとアクセスのログ記録の変数リファレンス
正規表現『*(アスタリスク)』の意味について

メソッドレスポンスに関しては、以下のように設定しています。

修正後の実行結果

想定通りステータス500かつレスポンスモデルで定義した状態でreturnされています!

Sun Oct 23 01:16:27 UTC 2022 : Method response body after transformations: {"Status": 500, "ErrorReason": "name 'hoge' is not defined"}
Sun Oct 23 01:16:27 UTC 2022 : Method response headers: {X-Amzn-Trace-Id=Root==0, Content-Type=application/json}
Sun Oct 23 01:16:27 UTC 2022 : Successfully completed execution
Sun Oct 23 01:16:27 UTC 2022 : Method completed with status: 500

Lambda関数のhoge(エラー)を無くした状態で実行。こちらも想定通りですね!

Sun Oct 23 01:21:26 UTC 2022 : Method response body after transformations: {"Status": "200", "ErrorReason": "None"}
Sun Oct 23 01:21:26 UTC 2022 : Method response headers: {X-Amzn-Trace-Id=Root=;Sampled=0, Content-Type=application/json}
Sun Oct 23 01:21:26 UTC 2022 : Successfully completed execution
Sun Oct 23 01:21:26 UTC 2022 : Method completed with status: 200

最後に

API Gateway+Lambda カスタム統合の構成にすると、設定が何かと複雑で私自身も苦労したので同じように悩んでいる方のお役に立てれば幸いです。

参考文献

API Gateway で Lambda プロキシ統合を設定する
API Gateway で Lambda 統合を設定する
API Gateway で統合レスポンスを設定する
API Gateway のメソッドレスポンスをセットアップする
HTTP カスタム統合の API Gateway でバックエンドのステータスコードと異なるコードが返ってくるのはなぜでしょうか
API Gateway マッピングテンプレートとアクセスのログ記録の変数リファレンス
正規表現『*(アスタリスク)』の意味について
API Gateway & Lambda Function(Python)でカスタムエラーのレスポンスを返す