AWS LambdaのCORSでハマったからreturnを共通化した

# はじめに
前回、AWS Lambdaを使用したサーバーレス開発について、私がハマった箇所をまとめました。
[インフラエンジニアがやってみた AWSでサーバーレス Webアプリケーション開発](https://qiita.com/wakoit/items/aef089dd04533025f142)

そこでCORSについて毎回returnでハマるのでreturnを共通化しました。
文中のAWS LambdaのソースはPython 3.8で書かれています。

# 言いたいこと
CORSでエラーが出たらLambdaのreturnを疑え。

# Why?
JavascriptのコンソールにCORSのエラーが出るとき、十中八九Lambdaで正しくreturnしていないからです。

CORSの仕組みや考え方は以下の参考ページが大変詳しいです。
[なんとなく CORS がわかる...はもう終わりにする。](https://qiita.com/att55/items/2154a8aad8bf1409db2b)

ある程度仕組みを理解して頂いたところで、CORSのエラーが出るパターンは以下になります。

* Lambda側でOPTIONSメソッドに応答していない
* Lambda側で応答していない or 応答している内容に不備がある

かぶっている部分もありますが、大枠でこの2つになると思います。
ポカレベルの内容ですが気づきにくく、結果ハマりました。

# returnを共通化する
まず、丁寧にreturnしないといけないので、それなりにreturn部の処理が長くなってしまいます。
なので共通化してreturnできるように2つの関数を作成しました。

```python:
# API Gatewayのルールに則った成功の response を生成する
def create_success_response(body, **kwargs):
    origin  = '*'
    methods = 'GET'
    
    for k, v in kwargs.items():
        if k == 'origin'  : origin  = v
        if k == 'methods' : methods = v 
    
    headers = {
        'Access-Control-Allow-Headers' : 'Content-Type',
        'Access-Control-Allow-Origin'  : origin,
        'Access-Control-Allow-Methods' : methods
    }
    
    logger.info(
        'return values headers = {}, body = {}, origin = {}, methods = {}'
            .format(headers, body, origin, methods)
    )
    
    return {
        'isBase64Encoded': False,
        'statusCode'     : 200,
        'headers'        : headers,
        'body'           : json.dumps(body)
    }

# API Gatewayのルールに則った失敗の response を生成する
def create_error_response(body, **kwargs):
    origin  = '*'
    methods = 'GET'
    
    for k, v in kwargs.items():
        if k == 'origin'  : origin  = v
        if k == 'methods' : methods = v 

    headers = {
        'Access-Control-Allow-Headers' : 'Content-Type',
        'Access-Control-Allow-Origin'  : origin,
        'Access-Control-Allow-Methods' : methods
    }
    
    return {
        'isBase64Encoded': False,
        'statusCode': 599,
        'headers': headers,
        'body': json.dumps(body)
    }
```

**`create_error_response`のほうは`statusCode`を`599`でハードコーディング**しています。
要件がある場合は、methodsと同じように引数で外部から変更できるようにしてください。
**originの内容もサイトのセキュリティポリシーに合わせて、URLを絞ってください。**


# OPTIONSメソッドに応答していない
LambdaがOPTIONSに対してちゃんと応答しているか疑いましょう。
GET, POST, PUT, DELETE…などなどは応答していたけどOPTIONSを忘れていた、という事があると思います。
というか私がそうでした。

はい、OPTIONSについての処理を追加しましょう。

```python:OPTIONSメソッドをちゃんと処理しよう
#=======================================================
# OPTIONS : CORSに必要
#=======================================================
elif event['httpMethod'] == 'OPTIONS':
    logger.info('handle the options method.')
    
    return create_success_response(
        { 'message': 'successfully: called options method.' },
        methods='GET,PATCH,DELETE'
    )
```

この時大事なのが、**methodsにLambdaで受け付けるHTTP Methodをすべて書く**ことです。
PUT, PATCH, POSTなど細かな違いもちゃんと仕様に合わせて書きましょう。

# 応答していない or 応答している内容に不備がある
returnを書いてるから応答してる！と思ったら大間違いです！
return時にちゃんと、CORSの仕様に則った応答を返す必要があります。
具体的に言うと`create_success_response`、`create_error_response`関数の`headers`の部分です。
returnを共通化したので、return時に共通化した関数を呼んであげれば大丈夫になりました。

```python:return時に共通化した関数を呼ぶ
return create_success_response(
    response,
    methods='GET'
)
```

どこの処理でもreturn時に関数を呼ぶようにしています。
共通化した関数を経由することで、期待されているレスポンスに適切にフォーマットした後にreturnすることが出来るようにしました。

# 最後に
適切なフォーマットでreturn出来ていないことが毎回のハマりポイントでした。
returnの処理を共通化することでミスに気づきやすくなりました。

javascript consoleに出力されるCORS errorにさよなら！