Lambda関数に手を加えず、API GatewayのLambdaプロキシ統合を切り替えられるか

# はじめに

AWS APIGatewayとLambdaを使ったサーバレスのシステムで、Lambdaプロキシ統合を使用することで、API Gateway側で、Lambda側へ渡すパラメータを細かく設定することなく、Lambda側でコントロールすることが出来るメリットがあります。

このLambdaプロキシ統合を使った連携を前提に開発を行う場合、Lambdaでは、この仕様に沿ったインプットとアウトプットを実装をする必要があります。

参考までに、Lambdaプロキシ統合を利用する場合、Lambdaでは以下のようなインプットに対応した実装が必要です。

リクエストが以下のような場合

```sh
curl -XPOST \
     -H "X-Param-Header:2" \
     -d '{"param": 3}' https://YourApiDomain/xxx?param=1 
```

Lambdaに渡されるパラメータ（*event*）は

```json
{
    "httpMethod": "POST",
    "headers": {
        "X-Param-Header": " 2"
    },
    "queryStringParameters": {
        "param": "1"
    },
    "pathParameters": {
        "val": "xxx"
    },
    "body": "{\n    \"param\" :3\n}"
}
```

のような形で渡ります（他にも渡されるパラメータがいくつかありますが、省略しています）。

Lambdaでは、このeventからリクエスト時のパラメータを取得し、Lambda内でコントロールし、アウトプットでは、以下のように結果をJson形式で返却します。

```json
{
    "statusCode": 200,
    "body": "OK"
}
```

このLambdaのレスポンスに対して、API Gatewayでは、statusCodeをHTTPレスポンスコードとして返し、bodyの内容をレスポンスボディとしてAPIの呼び出しもとに返却します。

# 課題

![課題](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297616/7ce54cd9-a919-79c3-44b9-49b501d562a3.png)

- API Gateway（A）とLambdaが、Lambdaプロキシ統合で連携しているサーバレス環境のLambdaに対して、別のAPI Gateway（B）からLambdaを利用しようとしている。
- Lambdaは、Lambdaプロキシ統合による利用を前提とした実装となっている。
- API Gateway（B）のインタフェースは、API Gateway（A）と異なっている。例えば、API Gateway（A）ではparamをパスパラメータで渡しているが、API Gateway（B）では、クエリストリングでparamで渡すことが決まっている等。

今回、API Gateway（B）からLambdaを利用するために、どのような対応をする必要があるのかを検証しました。

また、API Gateway（A）への影響を回避するため、**Lambda関数のロジックに対して変更は加えない**ことが前提となっています。

# Lambdaプロキシ統合を使用せず、リクエストとレスポンスをAPI Gatewayで加工する

Lambda関数に変更を加えないことを前提としているので、変更が必要なことは、API Gatewayで吸収する必要があります（他のサービスを利用しない場合）。
まずは、API GatewayからLambdaに渡すパラメータをLambdaプロキシ統合のパラメータに合わせ、レスポンスについても、Lambdaプロキシ統合に対応したLambdaから帰ってくるレスポンスに合わせるように、API Gatewayの設定変更を行います。

## 統合リクエスト

はじめに、**Lambdaプロキシ統合**を無効にします。

次に、マッピングテンプレートを追加しますが、各パラメータをLambdaプロキシ統合を使った場合と同じフォーマットでLambdaにパラメータが渡るように定義します。
クエリ文字列は `queryStringParameters`、パスは `pathParameters`、ヘッダーは `headers`、リクエスト本文は `body`になります。

- マッピングテンプレート
  - リクエスト本文のパススルー : テンプレートが定義されていない場合
  - Content-Type : application/json

```
#set($allParams = $input.params())
{
  #set($params = $allParams.get('querystring'))
  #if($params.isEmpty())
  "queryStringParameters" : null,
  #else
  "queryStringParameters" : {
    #foreach($paramName in $params.keySet())
      "$paramName" : "$util.escapeJavaScript($params.get($paramName))" #if($foreach.hasNext),#end
    #end
  },
  #end
  #set($params = $allParams.get('header'))
  #if($params.isEmpty())
  "headers" : null,
  #else
  "headers" : {
    #foreach($paramName in $params.keySet())
      "$paramName" : "$util.escapeJavaScript($params.get($paramName))" #if($foreach.hasNext),#end
    #end
  },
  #end
  #set($params = $allParams.get('path'))
  #if($params.isEmpty())
  "pathParameters" : null,
  #else
  "pathParameters" : {
    #foreach($paramName in $params.keySet())
      "$paramName" : "$util.escapeJavaScript($params.get($paramName))" #if($foreach.hasNext),#end
    #end
  },
  #end
  #if($input.body == {})
  "body" : null
  #else
  "body" : "$util.escapeJavaScript($input.body)"
  #end
}
```

ポイントとして、各パラメータが未定義の場合は、Lambdaプロキシ統合だと null として渡されるので、定義がない場合には、あえて `null` を指定しています。

## レスポンス統合

Lambdaプロキシ統合に対応したLambdaでは、Json形式で返却しますが、必要な値はLambdaからのレスポンスの**body**の部分を取得するために、マッピングテンプレートを追加します。

- Lambda エラーの正規表現 : default（空白）
- メソッドレスポンスのステータス : 200
- コンテンツの処理 : パススルー
- マッピングテンプレート
    - Content Type : application/json

```
$input.path('$.body')
```

※API Gatewayのメソッドレスポンスで、HTTPステータス 200 を先に定義しておく必要があります。

## 問題点

Lambdaプロキシ統合に適したLambdaでは、HTTPレスポンスコードは、Lambdaのレスポンスで指定してあげる。例えばHTTPレスポンスコードを 500 で返したい場合には、Lambdaは以下のようなレスポンスをAPI Gatewayに返却します。

```json
{
    "statusCode": 500,
    "body": "NG"
}
```

そのため、Lambdaレスポンス統合を使用していないAPI Gatewayでは、正常にレスポンスが返されたという判断で、HTTPレスポンスコードは200で処理してしまいます。

では、統合レスポンスの **Lambda エラーの正規表現** で拾うことは出来るか？というと出来ない。ここでエラーの正規表現が拾うのは、Lambdaで例外が発生したエラーメッセージに対して正規表現を行います。
今回は、**Lambda関数のロジックに対して変更は加えない**という前提があるため、API Gatewayのレスポンス統合だけのコントロールでは、難しいと考えられます。

# まとめ

上記のことから、Lambdaプロキシ統合に対応したLambdaと、Lambdaプロキシ統合を使用しないAPI Gateway。および、Lambdaプロキシ統合に対応していないLambdaと、Lambdaプロキシ統合を使用したAPI Gatewayを連携させようと思った場合に、API Gatewayで吸収させようとするのは難しいのかと思われました。

そのため、課題に上げられたようなケースで、Lambdaへの変更を行わないような制約がある場合、API GatewayでLambdaプロキシ統合の利用はそのままとし、API GatewayとLambdaとの間に、インタフェースを吸収するためのLambda関数を用いることが現実的なのかと思われる無難な結果となりました。

# さいごに

今回、以下のケースのサンプルを作成しましたので、実際に動かしてみたい方は [**テンプレート**](https://github.com/ot-nemoto/UseLambdaProxyIntegrationDemo) をデプロイしてみてください。

- Lambdaプロキシ統合を使用したAPI Gatewayと、Lambdaプロキシ統合に対応したLambdaのサーバレス環境

![](https://raw.githubusercontent.com/ot-nemoto/UseLambdaProxyIntegrationDemo/images/UseLambdaProxyIntegrationDemo-use-lambda-proxy-integration-demo.png)

- Lambdaプロキシ統合を使用しないAPI Gatewayと、Lambdaプロキシ統合に対応したLambdaのサーバレス環境（エラーハンドリングが出来ていない）

![](https://raw.githubusercontent.com/ot-nemoto/UseLambdaProxyIntegrationDemo/images/UseLambdaProxyIntegrationDemo-unuse-lambda-proxy-integration-demo.png)

- Lambdaプロキシ統合を使用しないAPI Gatewayと、Lambdaプロキシ統合に対応したLambda、およびその間でエラーハンドリングを行うLambdaのサーバレス環境

![](https://raw.githubusercontent.com/ot-nemoto/UseLambdaProxyIntegrationDemo/images/UseLambdaProxyIntegrationDemo-unuse-lambda-proxy-integration-ex-demo.png)