Kinesis Data Streams プロキシとしてのAPI Gatewayの利用

# はじめに

以下のドキュメントを参考に、ユースケースとしてセンサーデータなどをWEB APIにGET もしくは POSTで送信しているリクエストをKinesis Streamに送ってみる方法を試しました。

[Amazon Kinesis プロキシとして API Gateway API を作成する](https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/integrating-api-with-aws-services-kinesis.html)

いくつかの実装例が記載されていますが、具体的には以下のAPIを作成します。

```
API の /streams/{stream-name}/record リソースで HTTP GET/POST メソッドを公開し、このメソッドを Kinesis の PutRecord アクションと統合します。
これにより、クライアントは名前付きストリームに 1 つのデータレコードを追加できます。
```

## ロールの作成

API GatewayがKinesisにアクセスするためのIAMロールを作成します。

* 使用するサービスで「API Gateway」を選択します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/228453/0dfbcf29-2258-5ef5-ac05-0a0e0c449ab9.png)

* ここはデフォルトのままで

![image.png](https://qiita-image-store.s3.amazonaws.com/0/228453/79f433dd-c8c9-234e-a80f-d35a80f42449.png)

* タグ設定も任意でOKです

![image.png](https://qiita-image-store.s3.amazonaws.com/0/228453/f84ee369-97d7-7f92-a853-65f9718cf7d9.png)

* ロール名は任意ですが、ここでは「apigateway-kinesis-stream-role」にしました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/228453/81d04ea5-05d1-4c6b-70c7-b34eb80aaa2f.png)

* ロールを作成したあとに、Kinesisで読み取りおよび書き込みアクションを有効にするため、「AmazonKinesisFullAccess」ポリシーを付与します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/228453/326363e8-c819-ba98-ecfe-8d2b13ff88bb.png)

ロールの準備はこれで終わりです。

## Amazon Kinesis Streamsの作成

Amazon Kinesis Streamsを作成します。
ストリームの名前は任意です。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/228453/1a1c9ce7-0d42-42b2-18c2-5b82904304d1.png)

## API Gateway の作成

API Gateway を作成します。
プロトコルは`REST`を選択し新しいAPIを作成します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/228453/84cc015e-c161-f788-359f-d39c830d6009.png)

### リソースの作成

> `/streams`リソースを`API`のルートに追加します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/228453/ba80501d-5395-b9a2-6b5d-c247dd01b089.png)

> `/streams`リソースの下に`{stream-name}`パス変数を使用して、子リソースを作成します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/228453/72c690b6-cff3-9be9-3727-370a8ef874c3.png)

> 名前付きストリームリソース`/{stream-name}`に`record`リソースを追加します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/228453/13afb107-5c32-c803-767c-01d157b9eae3.png)

> `GET`メソッドを追加し、以下を入力します。

| 項目 | 値 |
| :-- | :-- |
| 統合タイプ | AWSサービス |
| AWSリージョン | ap-northeast-1 |
| AWSサービス | Kinesis |
| AWSサブドメイン | (ブランク) |
| HTTPメソッド | POST |
| アクション | PutRecord |
| 実行ロール | 先程作成したロールをARNで入力 |

![image.png](https://qiita-image-store.s3.amazonaws.com/0/228453/cf64fd53-191b-552e-c340-7a5762d4bea2.png)

> HTTPヘッダーセクションに以下を入力します。

| 項目 | 値 |
| :-- | :-- |
| Content-Type | 'x-amz-json-1.1' |

![image.png](https://qiita-image-store.s3.amazonaws.com/0/228453/fa21bd0a-6d93-9185-2160-99ac56d55371.png)

> マッピングテンプレートセクションで以下を入力します。

* 「テンプレートが定義されていない場合(推奨)」を選択
* 【マッピングテンプレートの追加】を選択し、`Content-Type`に`application/json`を入力する

![image.png](https://qiita-image-store.s3.amazonaws.com/0/228453/4d5f0a59-5c50-304e-795f-27f1732da801.png)

* テンプレート入力欄に以下を入力します。

```text
#set($allParams = $input.params())
#set($params = $allParams.get('querystring'))
#set($data = "{#foreach($paramName in $params.keySet())""$paramName"": ""$util.escapeJavaScript($params.get($paramName))""#if($foreach.hasNext),#end#end}")
{
    "StreamName": "$input.params('stream-name')",
    "Data": "$util.base64Encode($data)",
    "PartitionKey": "$input.params('partitionkey')"
}
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/228453/19f6dfde-fef6-cdf3-8b6b-1cbaa970e457.png)

テンプレートでは、クエリ文字列から`PutRecord`アクションに必要な`StreamName`、`Data`、`PartitionKey`をマッピングしています。

API gatewayの設定は以上です。

## テスト

ここまででテストをしてみます。
以下の情報を入力し、テストしてみましょう。

| 項目　| 値 |
| :-- | :-- |
| パス {stream-name} | iot-stream |
| クエリ文字列 {record} | partitionkey=testkey&param1=value1 |

![image.png](https://qiita-image-store.s3.amazonaws.com/0/228453/6269d2fa-e437-f339-fe60-2c55dbebea44.png)

画面右のように以下が返ってくれば成功です。

```json
{
  "SequenceNumber": "xxxxxxxxxxxxxxx",
  "ShardId": "shardId-000000000000"
}
```

統合リクエストには以下のデータが渡されます。

```json
{
    "StreamName": "iot-stream",
    "Data": "eyJwYXJ0aXRpb25rZXkiOiAidGVzdGtleSIsInBhcmFtMSI6ICJ2YWx1ZTEifQ==",
    "PartitionKey": "testkey"
}
```

## おまけ

Kinesis Streamをデータソースとして`payload`をコンソール出力するLambdaです。

```
const util = require('util');

exports.handler = (event, context, callback) => {
    console.log(`event: ${util.inspect(event, { depth: null })}`);
    
    for (const record of event['Records']) {
        console.log('test: ' + record.kinesis.data);
        const dataDecode = Buffer.from(record.kinesis.data, 'base64').toString('utf8');
        
        console.log(`payload: ${dataDecode}`);
    }
    
    callback(null, 'Success');
}
```

## まとめ

ユースケースとしてセンサーデータなどをWEB APIにGETで送信しているリクエストをKinesis Streamに送ってみる方法を試しました。
GETでリクエストすることがあるかどうかはおいといて、クエリ文字列をかき集めて統合リクエストにマッピングすることが出来ました。