やたらと長い表題になってしまったがこのとおり。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にする。
手順
ざっくりとした手順。
- DynamoDBでテーブルを準備する
- Lambdaで関数の作成
- API Gatewayの設定を確認
- テスト用の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とか
- セキュリティ オープン
- 認証なし。誰でも呼び出せる状態。テストなので
- 新規APIの作成
関数の作成。
以下のようにコードを書き換える。エラー処理などはほとんどしていない。
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は適当。
これがテストデータとして渡されて、イベントが実行されるわけだ。
で、実行してstatusCodeで200が返ってきたらOK。
userとしてsuperman、さらに実行した日時と、statusとしてsuccessの文字列を返している。
ついでにDynamoDBも見に行って、testテーブルにsupermanのデータが格納されていることも確認しておく。
ここまでできたら、LambdaはOK。
API Gatewayの設定を確認
「サービス」→「ネットワーキング&コンテンツ配信」からAPI Gateway。前節の関数の作成の時にAPIも作っているので、確認する。
作成したAPIのステージから、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の構文ミス
参考
ありとあらゆるところでハマった。
ありがとうございました……。
- 初めてAWS Lambda+API Gatewayをやってみてハマったポイント 返却JSON形式、CORS設定
- CORS / lambda issues 502 · Issue #4614 · serverless/serverless · GitHub
完全ソリューションガイドのほうは、概要を掴むのに参考にはなるがちょっと古い。そのまま実行はできない。CORSでハマる。サーバーレスアプリケーション開発ガイドのほうは、コードがpythonで助かった。
コメント