【AWS】APIGatewayでLambdaのアプリ側で発生した例外処理を500エラーで返したい。


## 背景

JavaアプリをLambdaでホストしているが、API Gateway経由でビジネスロジック側のエラー処理した際、ステータス上200で返ってきてしまい、困ったので調べた

## TL;DR

Javaだと例外処理の返却値が標準エラーの形式と合ってないので、レスポンス内容にステータスコード(5XX系)を埋め込む
200系のレスポンスマッピングテンプレート側でステータスコードを条件分岐させてマッチすればオーバーライドするように処理させると500エラーにできる。

https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/apigateway-override-request-response-parameters.html#apigateway-override-request-response-parameters-override-response

サンプルコード(Github)

https://github.com/tukapai/simple-lambda-java/tree/main

## 確認手順

以下は、検証のために確認した手順を記載しておく

### 前提条件

以下構成なっていることを前提とする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89244/c1f3ab01-4b16-904c-d036-d39b88b803ea.png)

### Lambda側設定

#### LamdaHanderの作成

検証のため例外を強制発生するコードを作る。
フレームワーク依存な情報を排除したかったので、AWS公式ドキュメントのプレーンな実装とした。

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/java-handler.html

```Java

package org.example;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;

import java.util.Map;

// Handler value: org.example.exampleHandler
public class exampleHandler implements RequestHandler<Map<String,String>, APIGatewayProxyResponseEvent>{

    @Override
    public APIGatewayProxyResponseEvent handleRequest(Map<String,String> event, Context context)
    {
        APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent();
        try{
            // エラーの検証のためRuntimeExceptionを発生させる。
            throw new RuntimeException();
        }catch (RuntimeException e){
            response.setStatusCode(500);
            response.setBody("Error: SampleHandler is failed");
            return response;
        }
    }
}

```

#### gradleの設定

AWS公式からLambdaで必要なライブラリの指定があるのでdependenciesに追加する。

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/java-package.html#java-package-libraries

```gradle

plugins {
    id 'java'
}

group = 'org.example'
version = '1.0-SNAPSHOT'

repositories {
    mavenCentral()
}

dependencies {
    testImplementation platform('org.junit:junit-bom:5.9.1')
    testImplementation 'org.junit.jupiter:junit-jupiter'
    implementation(platform("software.amazon.awssdk:bom:2.21.1"))
    // 以下3つが必要
    implementation 'com.amazonaws:aws-lambda-java-core:1.1.0'
    implementation 'com.amazonaws:aws-lambda-java-events:3.11.1'
    runtimeOnly 'com.amazonaws:aws-lambda-java-log4j2:1.5.1'
    // https://mvnrepository.com/artifact/com.amazonaws/aws-java-sdk-core
    implementation 'com.amazonaws:aws-java-sdk-core:1.12.694'

}

tasks.register('buildZip', Zip) {
    from compileJava
    from processResources
    into('lib') {
        from configurations.runtimeClasspath
    }
}

    test {
        useJUnitPlatform()
    }

java {
    sourceCompatibility = JavaVersion.VERSION_17
    targetCompatibility = JavaVersion.VERSION_17
}

build.dependsOn buildZip

```

#### Lambdaに載せるファイルの生成

Lambdaに載せるファイルは`./gradlew build`でzipファイルとして出力できる。

(何も考えないでBuildしたらbuild/distributions配下にZipが生成された)

### API Gatewayの設定

API Gatewayで任意のリソースを作成する。

統合レスポンスのdefaultを編集

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89244/e350eea6-7094-c598-cbba-d1c1bbac658d.png)

Content Typeを`application/json`を指定

マッピングテンプレートを以下のような形にする

```
#set($inputRoot = $input.path('$'))
$input.json("$")
#if($inputRoot.statusCode.equal(500))
#set($context.responseOverride.status = 500)
#end
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89244/c4e4cafa-4320-aa59-f9f8-d63f0d264240.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89244/f2ebea03-e6d3-2289-442a-3cdf08566767.png)

API を任意のステージにデプロイして外部からリクエストできるようにする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89244/c21e6c03-fa59-e22f-70ac-2006f100ef14.png)



### 動作確認

Curlコマンドなどを利用してHTTPプロトコルのステータスが500となっているかを確認

```

simple-lambda-java on  master [!+] via 🅶 v8.2 via ☕ v19.0.1 on ☁️ Tokyo 
❯ curl -v  https://1pkko26y4k.execute-api.ap-northeast-1.amazonaws.com/dev/example   
<長いので省略> 
> GET /dev/example HTTP/2
> Host: 1pkko26y4k.execute-api.ap-northeast-1.amazonaws.com
> User-Agent: curl/8.4.0
> Accept: */*
> 
< HTTP/2 500 ← ここが500エラーになっていればOK
< date: Sat, 06 Apr 2024 04:13:05 GMT
< content-type: application/json
< content-length: 59
< x-amzn-requestid: cce8041d-b308-470f-b61c-5ec7e2c417f0
< x-amz-apigw-id: VyTIuFOLNjMEFmw=
< x-amzn-trace-id: Root=1-6610cbd1-01d4efd83c4734467fbbd06a;Parent=456b75f92567940f;Sampled=0;lineage=9d43bcc4:0
< 
{"statusCode":500,"body":"Error: SampleHandler is failed"}
* Connection #0 to host 1pkko26y4k.execute-api.ap-northeast-1.amazonaws.com left intact

```

ビジュアルで見る場合はPostmanなどを使うとステータスがわかりやすいので便利

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89244/f04ec784-4b8e-f32b-c6f1-c97ffd43eb24.png)


## その他参考資料

JavaでLambdaを使うユーザが少ないせいか、新しいバージョンに対応してないドキュメントが多い

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/java-exceptions.html

https://repost.aws/ja/knowledge-center/api-gateway-status-codes-rest-api

https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/handle-errors-in-lambda-integration.html