【AWS】API GatewayからLambdaを非同期で実行する

![diagram.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44249/f2f8dbca-e950-0b30-ae6b-71be7e0ab122.png)
# はじめに
最近Web APIを素早く用意するためにAWSのAPI Gateway + Lambdaの構成をよく使います。
しかし、サービスの質の向上や、仕様のためAPIはレスポンスを早く返さなければならない場合が多々あります。
ネットで検索すると、API GatewayからInvocationTypeを指定してLambdaを非同期呼び出しする事で先にレスポンスを返してしまう話はよく出てきますが、Lambda側で必要な処理が書かれていない事が多く、大抵のテストコードではプロセスをキルされてしまい、Lambdaが処理を停止します。（今回はNode.jsの記事なので、Pythonなどでは必要ないのかも？）
なので設定例を自分用備忘録的に書き残しておきます。

画像が多くて見辛いかもしれません。

## 処理の流れ
1. API Gatewayがリクエストを受け取る
2. 非同期でLambdaを呼び出す
3. LambdaからPromiseを返す
4. API Gatewayがステータスコード:202のレスポンスを返す

## 今回の構成
### 利用サービス
- API Gateway
- Lambda
 - Node.js(8.10)

###構成図
![diagram.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44249/f2f8dbca-e950-0b30-ae6b-71be7e0ab122.png)

## 実際の設定
### 処理を行うLambdaの作成

テストコードでは少し重い処理を想定し、2秒後に処理を終了するようにしました。

```javascript:index.js
exports.handler = async (event, context, callback) => {
    let promise = new Promise (function (resolve, reject) {
        // 処理開始時にstartと時刻を出力
        console.log("start: " + (new Date()));
        // 2秒待ってから次の処理へ
        setTimeout(function(){
            resolve();
        }, 2000);
    })
    promise
    .then(function(){
        // 処理終了時にendと時刻を出力
        console.log("end: " + (new Date()));
        callback(null, {
            statusCode: 200,
            body: JSON.stringify('OK'),
        });
    });
    // Lambdaの呼び出し元へPromiseを返す
    return promise;
};
```

ここが重要なのですが、呼び出されたLambdaからPromiseを返さないと呼び出し元（本記事ではAPI Gateway）はレスポンスを待つ事になります。
試しに`return promise;`の部分をコメントアウトなどしてみてください。呼び出し元は処理を待たされます。

### API Gatewayの作成
今回はLambdaから新規APIを作成します。
![add_trigger.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44249/7cb6103a-84f5-b34e-2b5b-2ea4d4409064.png)

作成したらAPI Gatewayのコンソールで設定していきます。

Lambdaからそのまま作るとLambdaプロキシ統合が設定されています。
統合リクエストをクリックして、リクエストを設定していきます。
![integration_request.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44249/2d821519-d580-36d2-a7ce-3335f87ef7cd.jpeg)

![proxy_check.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44249/83834ec7-d709-9db0-87c0-2f411c855449.jpeg)

「Lambdaプロキシ統合の使用」のチェックを外します。
これで普通のLambda統合になり、HTTPヘッダーを入力できるようになります。

HTTPヘッダーを追加し
名前: `X-Amz-Invocation-Type`
マッピング: `'Event'`
を設定します。

![http_header.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44249/7d9f1f2a-61f4-5289-390e-c781621b517e.png)

※ マッピングはJSONPathで書く事になりますが、今回は常に非同期にしたいのでシングルクォーテーションで囲った固定値を書き込みます。

APIをデプロイしてアクセスしてみます。

Chrome拡張機能の[Restlet Client](https://chrome.google.com/webstore/detail/restlet-client-rest-api-t/aejoelaoggembcahagimdiliamlcdmfm)、簡単にテストできるので重宝してます。

![api_test.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44249/9df764b9-595d-549e-ef1a-6cb8a85a4925.jpeg)

リクエストを送ると2秒も待たずに134ミリ秒でレスポンスが返ってきました。

![cloudwatch_log01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44249/da1433a5-dbfd-4e0c-5216-ab07f2e247bf.jpeg)

ログを見るとLambdaはプロセスを維持していて2秒後にendを出力している事がわかります。

次は試しにLambdaのコードで`return promise;`をコメントアウトして実行したログ。
![cloudwatch_log02.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44249/29960c0f-0769-2f88-e85e-0adf52472165.jpeg)

endを出力する事なく23ミリ秒程度でプロセスが終了している事がわかります。

ひとまずこれで非同期処理は完成です。

### レスポンスの加工

このままだと常に200を返してしまうので、レスポンスのデフォルトHTTPステータスを202にします。
※ 今回は202以外使わない想定です。

メソッドレスポンス → 統合レスポンスの順で設定をします。
![response_before.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44249/dde9dcf0-d173-b2b5-226e-5456e286cf50.png)

#### メソッドレスポンス
デフォルトで入っている200を削除して202を追加します。
![method_before.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44249/67eba1c2-1517-bb37-44c4-57b9420476e8.png)
　▼
![method_after.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44249/9f54a7b6-3bfc-92a2-a17c-de079c15b941.png)

#### 統合レスポンス
こちらも同じく200を削除して202を追加します。
追加の際、正規表現には`-`（ハイフン）を入れます。
![integration_before.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44249/eb99b957-9d02-be7f-8e83-93f58e473ff8.png)
　▼
![integration_after.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44249/56c6dd15-b20e-624d-7971-8cd90637d043.png)

このようになります。
![response_after.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44249/925ec1e1-325e-bfca-8e76-fdbb487ab2a6.png)


APIを再度デプロイしてリクエストしてみます。
![api_test2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44249/494c1e5c-cc38-319e-5d51-1165d31b6d13.jpeg)
HTTPステータス:202が無事に返ってきました。

# おわりに
Lambdaを使って非同期処理をする場合、例えば最初のLambdaから他のLambdaなどの別の処理を呼び出す方が汎用性がありますが、並列処理をしない場合や、取り回しが複雑でない場合には直接非同期呼び出しをする方が実行回数も抑えられて良いかなと思います。