AWS API Gateway基本知識まとめ、Lambda連携手順

# 記事について

Amazon API Gatewayの基本情報まとめ

# Amazon API Gatewayの位置づけについて

さまざまな機能、処理をWeb APIとして提供
実際に処理を行うバックエンドとしてはLambda関数、各種AWSサービス、インターネット経由で呼び出し可能なURL+メソッド等

![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/1c3ed8a8-bf4d-73f5-4499-228b215f2108.png)

# Amazon API Gatewayの概要について

## APIの種類

API作成時に以下2つから選択

### REST

扱うリソースをURIとして定義し、それらをHTTPメソッド(PUT, GET, POST, DELETE, ... )の表現で操作

#### 主なHTTPメソッド

- GET : 取得
- POST : 追加
- PUT : 更新
- DELETE : 削除

### WebSocket

HTTPの上で、クライアントとサーバー間の双⽅向通信を実現するための通信仕様
1つのコネクション上で継続的なデータ送受信を行う（1通信毎にWebSocketは切断しない）



## エンドポイントタイプ

APIを使用するクライアントの場所に応じて決定
API作成時に以下3つから選択

### エッジ最適化

一旦CloudFrontにルーティング
APIクライアントが地理的に分散している場合に最適

### リージョン 

リージョンに直接ルーティング
APIクライアントが同じリージョンに固まっている場合に最適
クライアントが同じリージョンの場合にレイテンシ削減に有効
APIを作成したリージョンにAPIがデプロイされる

### プライベート

インターネットからのアクセス不可



## デプロイとステージ

APIはステージにデプロイすることで呼び出し可能になる

### ステージ

デプロイされる環境
製品向けのデプロイ環境を`prod`、開発向けのデプロイ環境を`dev`等で分けることが可能
ステージ名はURLの一部として使用される、以下ステージ名を`prod`にした場合の例

`https://<api-id>.execute-api.<region-id>.amazonaws.com/prod/`



## API作成方式

- 手動作成

- 既存APIのクローン

- API仕様を記載したSwaggerファイル（JSON, YAML）のインポート

  


## 料金

### API Gateway

条件：REST API、東京リージョン、キャッシュなし

|  リクエスト数 (月間)  |  価格 (100万 APIコールあたり)  |
| ---- | ---- |
|  最初の 3 億 3,300 万  |  4.25 USD  |
|  次の 6 億 6,700 万  |  3.53 USD  |
|  次の 190 億  |  3.00 USD  |
|  200 億以上  |  1.91 USD  |

月に1万回APIコールした場合、
  4.25 USD / 100 = 0.0425 USD

### データ転送

インバウンドデータ転送は無料
アウトバウンドデータ転送が発生する場合は別途料金が発生
基本的に1GB/月あたり0.9 USD


# API設定（REST）について

「/」を最上位としてツリー構造で「リソース」を定義する
「/」リソース配下にリソースを複数構成可能、配下のリソース名はURLの一部になる
"{}"でパスパラメータを指定可能
各リソースに受け付けるHTTPメソッド（GET, PUT, POST, DELETE...）を指定、複数指定可能
HTTPメソッドによって呼び出される処理を区別可能、区別したくない場合は"ANY"を選択

## API例

用途：デバイスの設定取得、更新

API名：DeviceSettingsOperations

| URI         | メソッド |                              |
| ----------- | -------- | ---------------------------- |
| /           | GET      | 全デバイスの設定取得         |
| /{deviceId} | GET      | 指定したIDのデバイス設定取得 |
| /{deviceId} | PUT      | 指定したIDのデバイス設定更新 |



## メソッド設定

リソースに設定した各メソッドに対して、
一連のフロー（クライアントからのリクエスト → バックエンドでの処理 → クライアントへのレスポンス）を設定する
以下のようにメソッドリクエスト、統合リクエスト、統合レスポンス、メソッドレスポンスの設定を行う

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/b0a09cf1-8c85-fef3-be42-e049222b1e6a.png" alt="x" width=80% />

### メソッドリクエスト

クライアントからのリクエスト受付に関する設定
以下のような設定を行う

- メソッドをコールするための認証（権限を持ったIAMユーザのみコール可能等）
- 受け付けるクエリパラメータ
- 必須とするHTTPヘッダ

※パスパラメータ：一意のリソース特定に使用するパラメータ、ID等
※クエリパラメータ：省略可能なパラメータ

### 統合リクエスト

バックエンドへのリクエストに関する設定
以下のような設定を行う

- リクエストの変換
- バックエンドの種類（Lambda, AWSサービス, HTTP, Mock, VPCリンク）

### 統合レスポンス

バックエンドからのレスポンスに関する設定
レスポンス内容の変換等

### メソッドレスポンス

リクエストに対する最終的なAPI Gatewayとしてのレスポンスに関する設定
レスポンス内のステータスコードや、レスポンスヘッダ等の設定

## プロキシ統合の使用（リクエスト、レスポンスの変換を必要としない場合）

リクエスト、レスポンスの変換を必要としない場合、プロキシ統合を使用すると、設定項目を大幅に減らすことが可能
バックエンドがLambda関数の場合、以下のようにLambdaプロキシ統合の使用にチェックを入れる

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/256aa86d-ea13-3ed5-ee8f-06f7eaa7e388.png" alt="image-20201016162532696" width=67% />

Lambdaプロキシ統合を使用した場合、
LambdaにInputされるデータ（Lambda関数のパラメータeventに入る値）が決まった形式になり、
LambdaからOutputするデータも決まった形式する必要がある

- 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

```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-encoded"
}
```

- 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

```json
{
    "isBase64Encoded": true|false,
    "statusCode": httpStatusCode,
    "headers": { "headerName": "headerValue", ... },
    "multiValueHeaders": { "headerName": ["headerValue", "headerValue2", ...], ... },
    "body": "..."
}
```



# 実際のAPI作成手順について

## 前提条件

### API設定

- APIタイプ：REST API
- エンドポイントタイプ：リージョン

### 作成API

API名：DeviceSettingsOperations

| URI         | メソッド |                              |
| ----------- | -------- | ---------------------------- |
| /           | GET      | 全デバイスの設定取得         |
| /{deviceId} | GET      | 指定したIDのデバイス設定取得 |
| /{deviceId} | PUT      | 指定したIDのデバイス設定更新 |

### メソッドリクエスト設定

- APIコール時の認証にAWS_IAMを使用

### 統合リクエスト設定

- バックエンドタイプとしてLambda関数を使用
- Lambdaプロキシ統合を使用

### クライアント

- Postmanを使用

  ※認証にAWS_IAMを使用する場合、署名バージョン 4 の署名をHTTPリクエストヘッダに乗せる必要がある
  ※バージョン 4 の署名はPostmanの機能で対応可能、またはSDKを使用して署名を作成可能

  

## AWSマネジメントコンソールでのAPI作成手順

### API作成

1．REST APIの構築をクリック

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/6d666cb2-f609-82b6-2021-215cb4ea3c32.png" alt="xx" style="zoom:67%;" />

2．API名を入力、エンドポイントはリージョンを選択

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/8f439796-5ec9-6c4b-68ab-75b11376134a.png" alt="image-20201016142124559" width=80% />

### リソースの作成

1．アクションからリソースの作成をクリック

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/61ae3e6d-74a2-92f2-8e3b-eef84449ebf9.png" alt="image-20201016171538032" width=67% />

2．リソース名を入力、パスパラメータとして設定する場合はリソースパス欄の値を"{}"で囲む

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/a1014810-72f4-fdbd-2edc-b27f9fc2464d.png" alt="image-20201016160205404" width=67% />

3．以下のようにリソースが追加される

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/5c65f9c3-6846-7da4-7576-e80e189c566b.png" alt="image-20201016160205405" width=50% />

### メソッドの追加

1．メソッドの作成をクリック

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/ce516a81-82a0-8a87-35aa-9f36fdf4508b.png" width=67% />

2．メソッドを選択し☑をクリック

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/30e6b698-3205-0967-7744-e432879394a2.png" alt="image-20201016172055518" width=67% />

3．統合タイプをLambda関数、Lambdaプロキシ統合を使用をチェック、実行するLambda関数名を入力

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/b6d43189-eae9-bf15-3c8d-d957684272f2.png" alt="image-20201016170221982" width=67% />

4．各リソース、各メソッドについて同様にメソッドを作成

### メソッドリクエスト設定

1．メソッドリクエストのリンクをクリック

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/6396411d-979b-41f5-44b0-1e649ee720e8.png" alt="xxx" width=67% />

2．許可欄でAWS_IAMを選択し☑をクリック

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/fb5d981e-b0ce-9751-4aec-e845d864a1d1.png" alt="image-20201016172542451" width=67% />
### CORS（Cross-Origin Resource Sharing）

ブラウザがオリジン(HTMLを読み込んだサーバのこと)以外のサーバからデータを取得する仕組み
Webアプリケーション等からAPI Gatewayにアクセスする場合、HTMLの読み込み先と、API Gatewayではオリジンが異なるためアクセスができない
その場合は、以下のCORSの有効化を行う

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/2d3a6fc4-b68f-7b19-3c04-06eecf1b4b01.png" alt="image-20201017094451653" width=50% />

Lambdaプロキシ統合を使用している場合は以下のようにLambda側でレスポンスヘッダを設定する必要がある

```python

    return {
        'statusCode': 200,
        'headers': {
            'Access-Control-Allow-Headers': 'Content-Type',
            'Access-Control-Allow-Origin': '*',
            'Access-Control-Allow-Methods': 'OPTIONS,POST,GET'
        }
    }
```
参考：
[AWS APIGateway + Lambda プロキシ統合　の利用でCORS設定にはまった話](https://qiita.com/maechi/items/069d37dc5599de013508)
[REST API リソースの CORS を有効にする](https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/how-to-cors.html)

### APIのデプロイ

1．APIのデプロイをクリック

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/36de1689-1ca0-c220-9449-c8c45a6ee6d6.png" alt="image-20201017094451653" width=50% />

2．任意のステージ名を指定してデプロイ

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/f345c246-93cc-0420-ea55-b0649355b466.png" alt="image-20201017094554197" width=50% />

3．ステージを選択、URLを確認

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/04c740d9-782d-5b22-9dd6-d3a59e74e5ac.png" alt="image-20201017094554197" width=50% />

### バックエンドとして動作するLambdaサンプル

- デバイス設定情報

```python
# device settings
deviceSettings = [
    {
        'deviceId': 1,
        'power': 'ON',
        'torque': 20,
        'angle': 45
    },
    {
        'deviceId': 2,
        'power': 'OFF',
        'torque': 12,
        'angle': 30
    },
    {
        'deviceId': 3,
        'power': 'ON',
        'torque': 90,
        'angle': 70
    }
]
```



1．全デバイスの設定取得

```python
import json

def lambda_handler(event, context):
    return {
        'statusCode': 200,
        'body': json.dumps(deviceSettings)
    }
```



2．指定したIDのデバイス設定取得

```python
import json

def getSettings(id, items):
    
    for item in items:
        if item['deviceId'] == id:
            return item
    return None

def lambda_handler(event, context):
    
    id = int(event['pathParameters']['deviceid'])
    item = getSettings(id, deviceSettings)

    return {
        'statusCode': 200,
        'body': json.dumps(item)
    }
```

以下Lambda単体でのテスト用データ

```json
{
  "pathParameters": {
    "deviceid": 1
  }
}
```



3．指定したIDのデバイス設定更新

```python
import json

def getSettings(id, items):
    for item in items:
        if item['deviceId'] == id:
            print(f'item : {item}')
            return item
    return None

def lambda_handler(event, context):
    
    id = int(event['pathParameters']['deviceid'])
    settings = json.loads(event['body'])
    
    item = getSettings(id, deviceSettings)
    
    for key in settings.keys():
        if key in item:
            item[key] = settings[key]

    return {
        'statusCode': 200,
        'body': json.dumps(item)
    }
```

以下Lambda単体でのテスト用データ

```json
{
    "pathParameters": {"deviceid": 1},
    "body": "{\"power\":\"OFF\",\"torque\": 50}"
}
```

### API Gattewayテスト

1．テストをクリック

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/dc1c61a8-18d7-7f69-2700-e978dc23f859.png" alt="axx" width=67% />

2．パスパラメータ、リクエスト本文を入力してテストボタンをクリック、右側に結果が表示される

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/2cd586e5-c80a-8315-c5f2-1ebe70a581fe.png" alt="image-20201017111957579" width=67% />

3．動作に不具合がある場合、Lambda単体でのテストや、CloudWatchで動作を確認する

## Postmanからのアクセス

1．Authorizationタブから、TYPEにAWS Signatureを選択、API Gatewayにアクセス可能なIAMユーザのアクセスキー、シークレットキーと、リージョン、サービス名を入力
2．メソッドを選択、URIを入力し、Sendをクリックし、正常に動作するとレスポンスが受信される

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695430/5ce842e4-7182-98e8-ad88-6a64da359233.png" alt="aaa" width=90% />

# 参考

https://www.youtube.com/watch?v=EpEETIox03s&t=580s