API GatewayでLambdaのエラーハンドリング

題材とするアーキテクチャ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406130/4fef8765-e979-d2bc-680c-cc04048b2d10.png)

# API Gatewayの返り値を整形する。

エラー時に400/500台のステータスコードを付してDescriptionをつけて返したい。

__1 Exceptionクラスを継承して拡張した例外クラスを作る。__
__2 エラー時にraiseする。__

```python:Lambda(1)
import json
import boto3
import logging
import traceback

logger = logging.getLogger()
logger.setLevel(logging.INFO)

dynamodb = boto3.resource('dynamodb')

# 例外クラスを拡張
class ExtendException(Exception):
    def __init__(self, statusCode, description):
        self.statusCode = statusCode
        self.description = description
    
    def __str__(self):
        obj = {
            "statusCode": self.statusCode,
            "description": self.description
        }
        return json.dumps(obj)
    
def lambda_handler(event, context):

    # validation check
    if not "key" in event or not isinstance(event["key"], int):
        raise ExtendException(400, "Bad Request")

    # 何らかの処理

    return 0
```

テスト

```json:response
{
  "errorMessage": "{\"statusCode\": 400, \"description\": \"Bad Request\"}",
  "errorType": "ExtendException",
  "stackTrace": [
    "  File \"/var/task/lambda_function.py\", line 29, in lambda_handler\n    raise ExtendException(400, \"Bad Request\")\n"
  ]
}
```

__3 API Gatewayのレスポンスをマッピング__

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406130/585608ed-d337-9a78-163a-cd64a4bc3df2.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406130/d6449dbc-9d46-d6c5-a55a-d3325b190602.png)

Lambdaエラーの正規表現では`.*statusCode: 400,.*`を指定。
Lambdaから返されるエラーレスポンスの`errorMessage`の部分のみ返している。

テスト

```json:response
{
  "statusCode": 400,
  "description": "Bad Request"
}
```


# 別Lambdaからコールした時にエラー内容を表示する。

以下のようにしてエラーレスポンスを拾うことができる。

```python
try:
    pass
except urllib.error.HTTPError as err:
    res = err.read().decode("utf-8")
```

```python:Lambda(2)
import json
import urllib.request, urllib.error
import logging
import traceback

logger = logging.getLogger()
logger.setLevel(logging.INFO)

class ExtendException(Exception):
    def __init__(self, statusCode, description):
        self.statusCode = statusCode
        self.description = description
    
    def __str__(self):
        obj = {
            "statusCode": self.statusCode,
            "description": self.description
        }
        return json.dumps(obj)
        
request_url = "https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/v1"
api_key = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

def lambda_handler(event, context):
    try:
        headers = {'x-api-key': api_key, "Content-Type":"application/json"}
        method = "POST"
        
        # validation errorするようにAPIをコールする。
        request_json = {
            "notExistKey": 1
        }
    
        req = urllib.request.Request(url=request_url, method=method, headers=headers, data=json.dumps(request_json).encode())
    
        with urllib.request.urlopen(req) as res:
            body = res.read().decode("utf-8")
            
    except urllib.error.HTTPError as err:
        traceback.print_exc()
        res = err.read().decode("utf-8")
        res = json.loads(res)
        logger.info("------------------")
        logger.info(res)
        logger.info("------------------")
        raise ExtendException(500, "[ERROR] {} : {}".format(res["statusCode"], res["description"]))
        
    except urllib.error.URLError as err:
        traceback.print_exc()
        res = err.read().decode("utf-8")
        res = json.loads(res)
        logger.info("------------------")
        logger.info(res)
        logger.info("------------------")
        raise ExtendException(500, "[ERROR] {} : {}".format(res["statusCode"], res["description"]))
    except:
        traceback.print_exc()
        raise ExtendException(500, "Internal Error")
        
    return json.loads(body)
```

テスト

```json:response
{
  "errorMessage": "{\"statusCode\": 500, \"description\": \"[ERROR] 400 : Bad Request\"}",
  "errorType": "ExtendException",
  "stackTrace": [
    "  File \"/var/task/lambda_function.py\", line 45, in lambda_handler\n    raise ExtendException(500, \"[ERROR] {} : {}\".format(res[\"statusCode\"], res[\"description\"]))\n"
  ]
}
```

---
追記 2020/08/06

# Lambdaのタイムアウトをハンドリングする。

上記の設定ではLambda(1)でタイムアウトした際にステータスコード200が返ってしまう。

```json
{
  "errorMessage": "2020-08-06T13:36:57.311Z 312a1819-af1a-4fc5-bb9c-f8ed8f573a9b Task timed out after 3.00 seconds"
}
```

統合レスポンスの正規表現では以下のように指定することでLambdaのタイムアウトをハンドリングできる。

```
.*"statusCode": 500,.*|.*Task timed out after.*
```