【Python】Malformed Lambda proxy response が出た時の対応

## 結論
レスポンスボディをJSON形式の文字列にしましょう。

OK

```
response = {'message': 'hello'}
return {
    'statusCode': 200,
    'body': json.dumps(response)
}
```

NG

```
response = {'message': 'hello'}
return {
    'statusCode': 200,
    'body': response
}
```

##  環境
* Python3.8

## 背景
インスタンスを起こすLambda関数をAPIゲートウェイから呼んでみるかー、となり作るも以下のエラーに苦しみました。
Lambdaでのテストは成功してAPIゲートウェイでのテストに失敗したという状況です。

```
# レスポンス本文
{
  "message": "Internal server error"
}

# レスポンスヘッダー
{"x-amzn-ErrorType":"InternalServerErrorException"}

# ログ
Execution log for request ...
(略)
Sat Sep 19 07:14:11 UTC 2020 : Received response. Status: 200, Integration latency: 2266 ms
Sat Sep 19 07:14:11 UTC 2020 : Endpoint response headers: ...
Sat Sep 19 07:14:11 UTC 2020 : Endpoint response body before transformations: ...
Sat Sep 19 07:14:11 UTC 2020 : Execution failed due to configuration error: Malformed Lambda proxy response
Sat Sep 19 07:14:11 UTC 2020 : Method completed with status: 502
```

この時点でlambda関数は以下の通りです。

```
import json
import boto3

def lambda_handler(event, context):
    region = event['pathParameters']['region']
    instances = [event['pathParameters']['instance']]
    ec2 = boto3.client('ec2', region_name = region)
    try:
        response = ec2.start_instances(InstanceIds=instances)
        return {
            'statusCode': 200,
            'body': response
        }
    except Exception as err:
        return {
            'statusCode': 500,
            'body': { 'message': f'{err}' }
        }

```

リージョンとインスタンスIDをパスパラメータで与えて、GETでリクエストを投げたら当該インスタンスが起動する、という内容です。

## Malformed Lambda proxy response イズ 何

詳しい説明は[公式ドキュメント](https://aws.amazon.com/jp/premiumsupport/knowledge-center/malformed-502-api-gateway/)にお願いします。
おおざっぱには、Lambdaのレスポンスの形式がAPIゲートウェイで扱えるものじゃないよー、と言っています。

上記ドキュメントに従って、レスポンスのreturnを書き換えます。
エラーの場合のレスポンスも同様に書き換えます。

```
        return {
            'isBase64Encoded': False,
            'statusCode': 200,
            'headers': {},
            'body': response
        }
```

結果...

```
Execution failed due to configuration error: Malformed Lambda proxy response
Method completed with status: 502
```

まだだめです。同じエラーが出ます。
[StackOverflowの類似の事例](https://stackoverflow.com/questions/47907641/malformed-lambda-proxy-response-from-aws-api-gateway-calling-a-lambda)によると、bodyの設定値がJSON文字列でないといけないようです。
地味に公式ドキュメントにも書いてありました。。。

> body フィールドは、もし JSON を返すのであれば、それ以上のレスポンスの問題を防ぐために文字列に変換されなければなりません。JSON.stringify を使用すると、Node.js 関数でこれを処理できます。他のランタイムでは異なる解決方法が必要ですが、概念は同じです。

という訳でbodyをJSON文字列に変換します。

```
        return {
            'isBase64Encoded': False,
            'statusCode': 200,
            'headers': {},
            'body': json.dumps(response)
        }
```

結果...

```
Successfully completed execution
Method completed with status: 200
```

通りました！！
これでURLへのアクセスひとつでインスタンスを起動できますね。

## 余談
レスポンスのフォーマット例として紹介されているフィールド(`isBase64Encoded`, `statusCode`など)はいずれも必須パラメータではありません。
なので、バイナリデータのやりとりをしないときは`isBase64Encoded`を設定する必要はありませんし、特につけるべきヘッダがないときは`headers`を設定する必要はありません。
あくまでも設定可能なフィールドであるようです。

## 最終的なLambda関数

```
import json
import boto3

def lambda_handler(event, context):
    region = event['pathParameters']['region']
    instances = [event['pathParameters']['instance']]
    ec2 = boto3.client('ec2', region_name = region)
    try:
        response = ec2.start_instances(InstanceIds=instances)
        return {
            'statusCode': 200,
            'body': json.dumps(response)
        }
    except Exception as err:
        return {
            'statusCode': 500,
            'body': json.dumps({
                'message': f'{err}'
            })
        }

```

パラメータのエラーは4xxで処理した方がいいけど、自分用なのでここまでです。

## 参考
* [API ゲートウェイの「不正な Lambda プロキシ応答」エラー、または 502 ステータスコードを解決するにはどうすればよいですか?](https://aws.amazon.com/jp/premiumsupport/knowledge-center/malformed-502-api-gateway/)
* [Output format of a Lambda function for proxy integration](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-output-format)
* [Malformed Lambda proxy response from AWS API Gateway calling a Lambda](https://stackoverflow.com/questions/47907641/malformed-lambda-proxy-response-from-aws-api-gateway-calling-a-lambda)