lambdaとAPI辺りの学習です。
外部APIをコールするlambdaをコールするAPI(via API Gateway) を意味もなく作る備忘録。
それだけだとつまらないので天気予報を取得し、取ってきたjsonをそのままDynamoDBに格納する構成を作る。
contents
- Node.jsでAPIをコールする方法。
- AWS環境でAPI Gateway + lambdaの設定方法。
- ローカル環境でAPI Gateway + lambdaの設定・実行方法。
事前準備
- OpenWeatherにSign UpしてAPIキーを入手。
- AWS SSM パラメータストアへURIとキーを登録。
SSMとDynamoDBのSDK(Node.js)の使い方
Node.jsからパラメータストアを参照
const AWS = require('aws-sdk');
const ssm = new AWS.SSM();
const res = ssm.getParameter({Name: "パラメータ名", WithDecryption: false});
//WithDecryptionは復号化の有無
Node.jsからDynamoDBへ書き込み
const AWS = require('aws-sdk');
const documentClient = new AWS.DynamoDB.DocumentClient({region: 'ap-northeast-1'});
const params = {
TableName: 'テーブル名',
Item: {
'属性1': '値1',
'属性2': '値2'
}
}
documentClient.put(params, (err, data) => {
if (err) console.log(err);
else console.log(data);
});
API Gatewayの設定
コンソールから作成する場合
- REST APIを選択。
- API名を新規作成。対象となるlambdaを選択して指定。
- [アクション]からメソッドの作成→POST
- テスト⚡︎をクリックし、POSTするJSONメッセージを入力したらテストボタン押下。右側にレスポンスが表示される。なお、このままデプロイしてURIを発行しても良いが、この状態だとどこからでもアクセスし放題なためセキュリティ的に不安。
- サイドバーの[リソース]から[メソッドリクエスト]を選択。APIキーの必要性を
true
に。 - [アクション]からAPIのデプロイを選択→ステージ名を選択or新規作成。→URIが発行される。
- サイドバーの[APIキー]からAPIキーを作成する。命名するのみ。
- サイドバーの[使用量プラン]を選択し、
関連付けられたAPIステージ
に6で作成したAPI名とステージ名を設定。APIキー
に8で作成したAPIキーを設定する。 - postmanからPOST。キーを設定しないと
{"message":"Forbidden"}
が返り、x-api-key
に指定して投げるとDynamoDBに記録され成功する。
DynamoDBの設定
AWS CLIから作成する場合
$ aws dynamodb create-table --table-name 'テーブル名' \
--attribute-definitions '[{"AttributeName":"プライマリキー名","AttributeType": "S"}]' \
--key-schema '[{"AttributeName":"プライマリキー名","KeyType": "HASH"}]' \
--provisioned-throughput '{"ReadCapacityUnits": 5,"WriteCapacityUnits": 5}'
設定値についての参考:https://qiita.com/propella/items/75e1a68468e37c3ebf45
$ aws dynamodb put-item --table-name テーブル名 \
--item '{"date":{"N":"20200110"},"test":{"S":"TEST"}}'
なお、次のエラーが出るのは以下の場合が考えられます。
Error parsing parameter '--item': Expected: '=', received: '"' for input:
- コマンドプロンプトから実行している場合。
→ シングルクオートを使うからいけないらしい。中身のダブルクオートをエスケープして、
"{\"date\":{\"N\":\"20200110\"},\"test\":{\"S\":\"TEST\"}}"
。 - 外側の
{}
を忘れている場合。
コード
const AWS = require('aws-sdk');
const ssm = new AWS.SSM();
const documentClient = new AWS.DynamoDB.DocumentClient({region: 'ap-northeast-1'});
const https = require('https');
const url = require('url');
const date = new Date();
const getWeatherInfo = async function (latitude, longitude, context) {
const ssmResultUri = await ssm.getParameter({Name: "OPENWEATHER_URI", WithDecryption: false}).promise();
const ssmResultKey = await ssm.getParameter({Name: "OPENWEATHER_APPID", WithDecryption: true}).promise();
const openWeatherUri = ssmResultUri.Parameter.Value;
const appid = ssmResultKey.Parameter.Value;
const endPoint = openWeatherUri + "?lon=" + longitude + "&lat=" + latitude + "&appid=" + appid;
const options = url.parse(endPoint);
options.method = 'GET';
options.headers = {'Content-Type': 'application/json'};
const req = https.request(options, (res) => {
res.on('data', async (chunk) => {
if (JSON.parse(chunk).cod == 200){
const params = {
TableName: 'weather_forecast',
Item: {
'date': date.getTime(),
'forecast': JSON.parse(chunk)
}
};
console.log("write DB");
await documentClient.put(params, (err, data) => {
if (err) console.log(err);
else console.log(data);
}).promise();
context.succeed("success");
}
});
});
req.on('error', (e) => {
console.log('problem with request: ' + e.message);
context.fail(e.message);
});
req.end();
};
exports.handler = function(event, context) {
console.log("lambda start");
getWeatherInfo(event.latitude, event.longitude, context);
console.log("lambda end");
};
localでAPIをテストする場合。
SAMでローカルにlambdaの実行環境を作成。
$ sam init --runtime nodejs10.x
SAM CLI + Swagger
※ SAMからのapi-keyの設定は未対応らしい。
https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-specification-api-gateway-extensions.html
swagger.ymlファイルを作成しAPIを定義し、template.ymlから参照する。なお、DefinitionUri
ではなくDefinitionbody
を使うならtemplate.yml内に続けて記述可能。
(swaggerについては、コンソールから作成したAPIがあればエクスポートすることができるためそれをベースに改良するほうが楽)
swagger: "2.0"
info:
description: "get weather info and insert to DynamoDB"
version: "1.0.0"
title: "get_weather_info"
basePath: "/dev"
schemes:
- "https"
paths:
/:
post:
produces:
- "application/json"
responses:
200:
description: "200 response"
schema:
$ref: "#/definitions/Empty"
security:
- api_key: []
x-amazon-apigateway-integration:
uri: "arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/arn:aws:lambda:ap-northeast-1:xxxxxxxxxxxx:function:getOpenWeatherInfo/invocations"
responses:
default:
statusCode: "200"
passthroughBehavior: "when_no_match"
httpMethod: "POST"
contentHandling: "CONVERT_TO_TEXT"
type: "aws"
definitions:
Empty:
type: "object"
title: "Empty Schema"
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: call open weather API function
Globals:
Function:
Timeout: 10
Resources:
getOpenWeatherInfo:
Type: AWS::Serverless::Function
Properties:
CodeUri: getOpenWeatherInfo/
Handler: index.handler
Runtime: nodejs10.x
Role: 'arn:aws:iam::xxxxxxxxxxxx:role/getWeatherInfoRole'
Events:
Api:
Type: Api
Properties:
Path: /dev
Method: post
getWeatherInfo:
Type: AWS::Serverless::Api
Properties:
StageName: dev
DefinitionUri: swagger.yml
ローカルのlambdaをAPIコールするにはAWS::Serverless::Api
の設定ではなくAWS::Serverless::Function
のEvents:
以下が重要。これがないと以下のエラーを生じる。
Error: Template does not have any APIs connected to Lambda functions
ローカルにAPIを立てる
$ sam build
$ sam local start-api
Mounting getOpenWeatherInfo at http://127.0.0.1:3000/dev [POST]
You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions, changes will be reflected instantly/automatically. You only need to restart SAM CLI if you update your AWS SAM template
2020-01-11 14:31:44 * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)
※追記
実際同じコードを用いてローカルのAPIを呼ぶと以下のエラーが出る。指示通りステータスコード等を含むオブジェクトをreturnしてあげると解決する。
localでやる場合とAWS環境でやる場合でレスポンスに求められる制限が違うのだろうか?
Function returned an invalid response (must include one of: body, headers, multiValueHeaders or statusCode in the response object). Response received: null