AWS Lambda + API Gateway でREST APIを作成し、値を渡してDynamoDBに書き込んでついでに返り値を得るサンプル

やたらと長い表題になってしまったがこのとおり。AWS Lambda + API Gateway でREST APIを作成する。API経由で値をjsonで渡してDynamoDBに書き込み、さらに返り値をjsonで得られるようなサンプルを作成する。Lambdaはpython3.6で書く。

すっごいハマった……。

目次

やること

REST APIを叩いて、DynamoDBに書き込みを行いたい。かつ、返り値を得て結果をホストマシンでも処理したい。

そのために、以下のような構成を組む。

構成

API Gateway + Lambda + DynamoDB。LambdaでDynamoDBに書き込みを行う。LambdaのトリガーをAPI Gatewayにする。

手順

ざっくりとした手順。

  1. DynamoDBでテーブルを準備する
  2. Lambdaで関数の作成
  3. API Gatewayの設定を確認
  4. テスト用のhtmlを作成して実行

以下に詳述。

DynamoDBでテーブルを準備

コンソール画面より、「サービス」→「データベース」→「DynamoDB」より、テーブルを作成する。ここでは、以下のようなテーブルを作成する。

  • テーブル名:test
  • プライマリキー
    • パーティションキー:user_id(文字列)
    • ソートキー:op_date(文字列)
  • テーブル設定…デフォルト設定の使用

DynamoDB の予約語 - Amazon DynamoDB」に注意する。userとかdateとか予約語。

Lambdaで関数の作成

コンソール画面より、「サービス」→「コンピューティング」→「Lambda」より関数の作成。

ここでは、設計図よりテンプレートを選ぶ。microserviceで検索して、「microservice-http-endpoint-python3」を選び、以下のように設定。

  • 名前:testFunction
  • ロール:テンプレートから新しいロールを作成
    • ロール名:testRoleName
    • ポリシーテンプレート:シンプルなマイクロサービスのアクセス権限
      • これでCloudeWatchとDynamoDBが使えるようになる
  • API Gatewayトリガー
    • 新規APIの作成
      • API名 test_api
      • デプロイされるステージ prod
        • prod…productの略。開発版ならdevとか
      • セキュリティ オープン
        • 認証なし。誰でも呼び出せる状態。テストなので

関数の作成。

以下のようにコードを書き換える。エラー処理などはほとんどしていない。

import boto3
import json
import datetime
import os

print('Loading function')
dynamo = boto3.resource('dynamodb')
table = dynamo.Table(os.getenv('TABLE_NAME'))

def respond(err, res=None):
    return {
        'statusCode': '400' if err else '200',
        'body': err.message if err else json.dumps(res),
        'headers': {
            'Access-Control-Allow-Origin' : '*',
            'Content-Type': 'application/json'
        },
    }


def lambda_handler(event, context):
    # print("Received event: " + json.dumps(event, indent=2))

    operation = event['httpMethod']
    body = json.loads(event['body'])

    if operation == 'POST':
        data = {
            "user_id"   : body['user'],
            "op_date"   : datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
            "status" : "success"
        }
        table.put_item(Item=data)
        return respond(None, data)
    else:
        return respond(ValueError('Unsupported method "{}"'.format(operation)))

テーブル名の読み込みでos.getenv()を使っている。環境変数はラムダのコード下に記述。

環境変数の記述

グローバルスコープにデータベースの接続など処理の重たいものを書いておくと、ウォームスタートの時に負荷が軽くて済む。

respond()は返り値を記述するメソッド。ここで注意したいのは、headersに含まれる'Access-Control-Allow-Origin' : '*'で、これがないとjqueryなどでうまく実行できない。理由はSame-Origin Policyという、同一ドメイン以外からのアクセスを制限するもので(でないと書き換え放題だし)、SOPとかCORSで調べたら色々出てくる。

lambda_handler()内がメインとなる部分で、eventに値が渡される。POSTの時だけdataをつくり、DynamoDBに書き込む処理を行う。そして、returnで返り値にもdata。

保存したら、一回テストしてみる。

テストイベントの設定で、イベント名を適当に決めて、以下のようにbody部分を"body": "{\"user\":\"superman\"}" とする。supermanは適当。

bodyの編集

これがテストデータとして渡されて、イベントが実行されるわけだ。

で、実行してstatusCodeで200が返ってきたらOK。

レスポンス

userとしてsuperman、さらに実行した日時と、statusとしてsuccessの文字列を返している。

ついでにDynamoDBも見に行って、testテーブルにsupermanのデータが格納されていることも確認しておく。

ここまでできたら、LambdaはOK。

API Gatewayの設定を確認

「サービス」→「ネットワーキング&コンテンツ配信」からAPI Gateway。前節の関数の作成の時にAPIも作っているので、確認する。

作成したAPIのステージから、URLの呼び出しを確認する。

URLの呼び出し

このhttps://〜 下に、リソース名(この手順だとFunctionの名前になっている)を付けたものが叩くべきAPIとなる。

ちなみに、ここでCORSの設定をすることもできるのだが、GETやPOSTを指定しないANYメソッドでは、その設定がきかない。公式ドキュメント「API Gateway リソースの CORS を有効にする - Amazon API Gateway」では以下のように説明される。

上記の手順をプロキシ統合の ANY メソッドに適用すると、適切な CORS ヘッダーは設定されません。代わりに、Access-Control-Allow-Origin などの適切な CORS ヘッダーを返すには統合バックエンドに依存します。

この手順だと、自動で作られるリソースのメソッドはANYであるため、Lambdaのほうでコードのheadersに自分でAccess-Control-Allow-Originを入れているのである。

テスト用のhtmlを作成

ユーザー名"local"を送信するだけのボタンを一つ設置する。

<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script>
function postdata() {
	var data = {
		"user" : "local",
	};
	$.post(
		"ここにtestFunctionのURI",
		JSON.stringify(data),
	).done( function(json_data) {
		console.log(JSON.stringify(json_data));
	});
return false;
}
</script>
</head>
<body>
<form onsubmit="return postdata();" id="myform">
	<input type="submit" value="送信">
</form>
</body>
</html>

送信ボタンを押すと、"user"として"local"をPOSTする。URIのところだけ適切に編集する。ここで、AWS Lambdaはjsonをそのまま受け取れないので、JSON.stringify()で文字列になおしていることに注意。

作成したhtmlをブラウザで開き、ボタンを押すと、POSTしたuser名localと、実行した時間、successの文字列が、jsonで返ってくることを、デベロッパーツールのコンソールで確認する。

実行結果

テストは通るのにこちらでエラーが出るとしたら、何かと間違えている。たとえば以下が怪しい。

  • Lambda関数側
    • Access-Control-Allow-Originをヘッダに含めるのを忘れている
  • ホストマシン側
    • 渡すべきdataを間違えている(jsonのままとか、キー名typoとか)
    • URIを間違えている(コピペミスとか、関数名じゃなくてAPI名つけているとか。この手順だとリソースが関数名になっているはず)
    • jqueryの構文ミス

参考

ありとあらゆるところでハマった

ありがとうございました……。

本サイトはUXを悪くするevilなGoogle広告を消しました。応援してくださる方は おすすめガジェット を見ていってください!
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次