【Python】API Gateway + lambda にファイルを post、s3 に保存

「Applibot Advent Calendar 2021」 6日目の記事になります！!どうぞよろしくお願いいたします。

# 1. 概要
ローカルにある csv を lambda に post して、s3にアップロードするまでのメモ。Content-Type の設定をAPI Gateway で行うことを知らず、若干詰まったので記事にしました。

# 2. 目次
- [1. 概要](#1-概要)
- [2. 目次](#2-目次)
- [3. aws リソースの用意](#3-aws-リソースの用意)
  - [3.1. ロールの用意](#31-ロールの用意)
  - [3.2. s3の作成](#32-s3の作成)
  - [3.3. lambda関数の作成](#33-lambda関数の作成)
  - [3.4. api gateway の作成](#34-api-gateway-の作成)
  - [3.5. 疎通の確認](#35-疎通の確認)
- [4. 参考](#4-参考)

# 3. aws リソースの用意
## 3.1. ロールの用意
   - 次のポリシーを持ったロールを作成する
     - AWSLambdaBasicExecutionRole
     - AmazonS3FullAccess

## 3.2. s3の作成
- 省略

## 3.3. lambda関数の作成
  - 3.1 で作成したロールを実行ロールとして指定
  - lambdaにデプロイするコードの作成
 
    ```python:send_csv2s3.py
    import io
    import cgi
    import json
    import boto3
    import base64


    BUCKET_NAME='bucket_name'
    DIRECTORY='test_dir/'

    s3 = boto3.resource('s3')


    def lambda_handler(event, context):

        body = base64.b64decode(event['body-json'])
        fp = io.BytesIO(body)

        environ = {'REQUEST_METHOD': 'POST'}
        headers = {
            'content-type': event['params']['header']['Content-Type'],
            'content-length': len(body)
        }

        bucket = s3.Bucket(BUCKET_NAME)
        fs = cgi.FieldStorage(fp=fp, environ=environ, headers=headers)
        for f in fs.list:
            print("filename=" + f.filename)
            bucket.put_object(Body=f.value, Key=DIRECTORY+f.filename)


        return {
            'statusCode': 200,
            'body': json.dumps('Hello World!!')
        }

    ```

## 3.4. api gateway の作成
  - REST API の構築から作成
  - アクションからメソッドの作成(POST)
    - 3.3 で作成した lambda を関数として作成
  - Content-Type: multipart/form-data を受け取る設定を追加
    - リソース>POST>マッピングテンプレート
      - リクエスト本文のパススルーは`テンプレートが定義されていない場合`を選択
      - Content-Type に `multipart/form-data` を入力しチェックマークをクリック
      - テンプレートの生成はプルダウンより `メソッドリクエストのパススルー` を選択
     ![API Gateway 2021-12-02 15-29-31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/349293/40e21bd9-2340-b590-2c71-da75f7faaeb4.png)

    - 設定>バイナリメディアタイプ
      - `multipart/form-data` を追加し保存
      
        ![API Gateway 2021-12-02 15-31-12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/349293/2843d896-376d-99c8-cb1d-b13307ed2852.png)

    - IPアドレス制限
      - リソースポリシーに以下を記述
      
        ```json
        {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Principal": "*",
                    "Action": "execute-api:Invoke",
                    "Resource": "arn:aws:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
                },
                {
                    "Effect": "Deny",
                    "Principal": "*",
                    "Action": "execute-api:Invoke",
                    "Resource": "arn:aws:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
                    "Condition": {
                        "NotIpAddress": {
                            "aws:SourceIp": "xxx.xxx.xxx.xxx/yy"
                        }
                    }
                }
            ]
        }
        ```

    - デプロイ
      - リソース>アクション>APIのデプロイ
     
## 3.5. 疎通の確認
- csv をポストするコード作成、実行

    ```python:send_csv.py
    import os
    import sys
    import json
    import requests
    from glob import glob
    from pathlib import Path


    def main():
        csv_dir = "./xxx/xxx"
        csvs = glob(csv_dir + "*.csv")
        a_csv_path = csvs[0]

        url = "https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/yyyy" # api gateway>ステージ>urlの呼び出しから確認する
        file_name = os.path.basename(a_csv_path)
        file = {'file': (file_name, open(a_csv_path, 'rb'), 'text/csv')}

        res = requests.post(url, files=file)
        print(res)
        print(res.text)

    if __name__ == "__main__":
        main()
   ```

- 結果の確認

![qiita.md — maruichi 2021-12-02 15-34-45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/349293/4283d474-ae7e-672a-2a2b-f982dbb3a31f.png)

完了！！（エラーハンドリング等については追記してください m・・m）


# 4. 参考
- [API Gateway の IP制限](https://aws.amazon.com/jp/premiumsupport/knowledge-center/api-gateway-resource-policy-access/)
- [API Gateway で multipart/form-data を受け取る](https://qiita.com/tetsu0831/items/220ba032116dc94063b3)