API Gateway + Lambda プロキシ結合の使用有無による違い

# やりたいこと
API GatewayとLambdaでREST APIを作成する際には、API Gateway側でLambdaプロキシ統合の使用有無を選べます。

![](https://i.imgur.com/IntxYF0.png)

今まであまり気にしていませんでしたが、開発する中で挙動の違いについて混乱してきたので、出力フォーマットとQueryStringの受け取り方という2点に絞って違いをまとめてみます。

今回は以下の通り2つのAPI Gatewayでプロキシ統合と非プロキシ統合の2つのエンドポイントを作って、それぞれの挙動の違いを調べてみました。

- 非プロキシ統合
![](https://i.imgur.com/Nn3WeV8.png)


- プロキシ統合
![](https://i.imgur.com/arRf5iI.png)

## 1. 出力フォーマットの違い

### 
簡単に言うとAPI Gatewayに対してLambdaから返されると想定している値が異なります。

- プロキシ統合の場合
  - Lambdaから返される値のフォーマットが決まっている(後述)

- 非プロキシ統合の場合
  - フォーマットは決まってなく、Lambdaから返される値がそのまま返される
  - マッピングテンプレートを使うことでAPI Gatewayから任意の形式で返すことも可能(この記事では扱いません...) 

### 実際に試してみた
以下の最初に生成されるLambdaをバックエンドとしてプロキシ統合と非プロキシ統合でレスポンスとしてブラウザに返される値の違いを調べてみました。

```python
import json

def lambda_handler(event, context):
    # TODO implement
    return {
        'statusCode': 200,
        'body': json.dumps('Hello World')
    }
```


結果は以下の通りで、Lambdaから返している値は同じでもブラウザ上に表示される値に差異が生じました。

- 非プロキシ統合の場合

![](https://i.imgur.com/tdVFmqP.png)


- プロキシ統合の場合

![](https://i.imgur.com/dl2ke98.png)



これはAWSの公式ドキュメントに記載がある通り、プロキシ統合の場合には以下の形式で値を返す必要があり、`body`に設定した値をResponse Bodyとして返してくれるからです。

> 
> ```json
>  {
>     "isBase64Encoded": true|false,
>     "statusCode": httpStatusCode,
>     "headers": { "headerName": "headerValue", ... },
>     "multiValueHeaders": { "headerName": ["headerValue", "headerValue2", ...], ... },
>     "body": "..."
>  }
> ```
> [プロキシ統合のための Lambda 関数の出力形式](https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-output-format)
> 

### この形式に従わなかった場合どうなるか

> 関数出力が別の形式である場合、API Gateway は 502 Bad Gateway エラーレスポンスを返します。
> 

試しにプロキシ統合のLambdaのコードを以下の通り変更してみたらInternal Server Errorが発生しました。

- 変更後のLambdaのコード

```python
import json

def lambda_handler(event, context):
    # TODO implement
    # return {
    #     'statusCode': 200,
    #     'body': json.dumps('Hello World')
    # }
    return json.dumps('Hello World')
```

- 出力結果

![](https://i.imgur.com/PUUElbe.png)


## 2. QueryStringの受け取り方
QueryStringとして`id`というキーを渡した時にそれにLambdaでアクセスする方法がどう違うのか調べました。
以下の通りエンドポイントが違うだけで、キーとバリューは全く同じ値を渡します。

- プロキシ統合
  - `/proxy-integration?id=15`

- 非プロキシ統合
  - `/non-proxy-integration?id=15`


### 2-1. プロキシ統合の場合
コードを以下の通り変更します。

```python
import json

def lambda_handler(event, context):
    # TODO implement
    return {
        'statusCode': 200,
        'body': json.dumps(event['queryStringParameters']['id'])
    }
```

- 出力結果
![](https://i.imgur.com/3lJyUzm.png)


#### なぜこのコードになるのか
プロキシ統合の場合、lambdaが受け取るeventパラメータからQueryStringなどのリクエストに含まれる情報にアクセスが可能だからです。

つまり、プロキシ統合の場合にはできるだけLambdaだけで開発が完結するようにAPI Gateway側での処理を最小限にしているという理解でいいと思います。

より詳細な情報は以下のAWSのドキュメントに記載してあります。


> Lambda プロキシ統合の場合、API Gateway は、次のようにクライアントリクエスト全体をバックエンド Lambda 関数の入力 event パラメータにマッピングします。
>
> ```json
> {
>     "resource": "Resource path",
>     "path": "Path parameter",
>     "httpMethod": "Incoming request's method name"
>     "headers": {String containing incoming request headers}
>     "multiValueHeaders": {List of strings containing incoming request headers}
>     "queryStringParameters": {query string parameters }
>     "multiValueQueryStringParameters": {List of query string parameters}
>     "pathParameters":  {path parameters}
>     "stageVariables": {Applicable stage variables}
>     "requestContext": {Request context, including authorizer-returned key-value pairs}
>     "body": "A JSON string of the request payload."
>     "isBase64Encoded": "A boolean flag to indicate if the applicable request payload is Base64-encode"
> }
> ```
> 
> [プロキシ統合のための Lambda 関数の入力形式](https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format)


### 2-2. 非プロキシ統合の場合

非プロキシ統合のLambdaのソースコードをプロキシ統合と同じものにすると以下のエラーが発生します。
プロキシ統合でやってくれていたeventへのマッピングは行ってくれていないので、「そんなキーないよ」と言われしまいます。


```json
{
errorMessage: "'queryStringParameters'",
errorType: "KeyError",
stackTrace: [
" File "/var/task/lambda_function.py", line 7, in lambda_handler 'body': json.dumps(event['queryStringParameters']['id']) "
]
}
```

ではどうするのかというとマッピングテンプレートというものを使ってAPI Gateway側で受け取った値をLambdaに渡すための設定を行います。

- API Gatewayでのマッピングテンプレートの定義  
統合リクエストよりマッピングテンプレートの定義を行います。

![](https://i.imgur.com/PTvFQZB.png)

- Lambdaのソースコードの変更  
マッピングテンプレートで渡された値はeventパラメータのキーとして指定可能なので、プロキシ統合とキーの指定方法を若干変更します。

```python
import json

def lambda_handler(event, context):
    # TODO implement
    return {
        'statusCode': 200,
        'body': json.dumps(event['id'])
    }
```

- 出力結果  
以下の通り、QueryStringとして渡された値にLambdaでアクセスできたことが確認できました。

![](https://i.imgur.com/nEaRUsQ.png)


## 3. まとめ
出力と入力という2つのポイントに絞ってプロキシ統合と非プロキシ統合の違いについてまとめてみました。

これ以外にも違いは色々あると思いますが、この2点に絞ってもイメージは掴めたかなと思います。

2つのやり方の違いは結局はAPI Gateway側に仕事を担当させるのか、Lambda側に処理を寄せるのかなので、ケースバイケースなのかなと思っています。
バックエンド側の知識の方が豊富ならプロキシ統合を使ってLambda側に処理を寄せてしまうという考え方もアリなのかなと思いました。

少しでも参考になれば幸いです。