概要
モジュール 3、4で使っている技術
(おまけ)サービス名がAWSで始まったりAmazonで始まったり
- 真偽は確かではないが、以下って聞いたことがある
- Amazonなんちゃらは単発で動かせるサービス
- AWSなんちゃらは他のサービスと連携して使うサービス
モジュール 3:サーバーレスサービスバックエンドの概要
- Lambda/DynamoDBを使ってWebアプリケーションのrequestを処理するためのバックエンドプロセスを構築
- 手順通りにやれば問題なし
- Lambdaのコードは後で読む(後述))
モジュール 4:RESTful API
- モジュール3で作成したLambdaをAPI Gatewayで公開する
- インターネット越しにパブリックアクセスできるが、Cognitoユーザプールで保護される
- CognitoユーザプールAuthorizer作成でエラーが発生した
レスポンス
Response Code(応答コード): 401
レイテンシー 153
Unauthorized request: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxx
- 理由はわからないがAuthorizerを再作成して「Cognito ユーザープール」を入力しないでプルダウンで選択する形にしたら動いた。。。
- endpoint typeの種類
- リージョン: 現在のリージョンにデプロイされる
- エッジ最適化: CloudFrontネットワークにデプロイされる。地理的に分散したクライアントへの接続時間が改善される
- プライベート: VPCからのみアクセス可能
Authorizer
- Lambda/Cognitoユーザプールを使用して、APIの承認ができる
- トークンのソース
- トークンの検証
- 設定してある場合、Cognitoで認証する前に正規表現を使用して受信トークンの検証を行う
- 複数のアプリから接続される場合、接続元の検証を行うっぽい(JWTのaudを検証?)
resource
- 「API Gateway CORS を有効にする」をチェック
- CORS(Cross-Origin Resource Sharing)
- 異なるドメインへのリソースへアクセスできるようになる仕組み
method
- 「Lambda プロキシ統合の使用」
- requestはLambdaにプロキシされる
- requestの詳細がhandler関数の「event」で参照できるようになる
プリフライトリクエスト
- こちらのページに詳しく解説がありました。
- はじめにOPTIONSメソッドによるリクエストを他のドメインにあるリソースに向けて送り、実際のリクエストを送信しても安全かどうかを確かめる
Lambdaに付与するIAMロールについて
- いつもLambdaを作る時に何となく付与していた「AWSLambdaBasicExecutionRole」を見てみる
- CloudWatch Logs関連のロールしかついてなかった
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "*"
}
]
}
インラインポリシー
- IAMポリシー一覧には出てこない模様
- 設定したIAMロールにのみ表示
メインディッシュ
Lambdaのコードを読む
全体の構成
- const(定数定義)
- exports.handler
- function findUnicorn()
- function recordRide()
- function toUrlString()
- function errorResponse()
const(定数定義)
// Node.jsでセキュアのランダムな文字列を生成するクラスのオブジェクトを生成
const randomBytes = require('crypto').randomBytes;
// 我らがaws-sdkの読み込み
const AWS = require('aws-sdk');
// DynamoDBのドキュメント管理をするクライアントオブジェクトを生成
// @see https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html
const ddb = new AWS.DynamoDB.DocumentClient();
// fleetって、艦隊?
// Map状を動くUnicornの名前とかのデータJSON
// 「Rocinante」は・・・俺の地元のingress agentの名前
const fleet = [
{
Name: 'Bucephalus',
Color: 'Golden',
Gender: 'Male',
},
{
Name: 'Shadowfax',
Color: 'White',
Gender: 'Male',
},
{
Name: 'Rocinante',
Color: 'Yellow',
Gender: 'Female',
},
];
callback関数について
function errorResponse()
- エラーレスポンスをコールバックするための関数の模様
// 引数
// errorMessage: エラーメッセージ
// awsRequestId: AWSへのリクエストID
// callback: コールバック関数
function errorResponse(errorMessage, awsRequestId, callback) {
// コールバック関数に以下を渡す
// Lambda関数の失敗の実行結果: null
// 関数の実行結果
callback(null, {
// ステータスコード500
statusCode: 500,
// エラーメッセージとリクエストIDのJSON
body: JSON.stringify({
Error: errorMessage,
Reference: awsRequestId,
}),
// ヘッダー
headers: {
'Access-Control-Allow-Origin': '*',
},
});
}
function toUrlString()
function toUrlString(buffer) {
return buffer.toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
}
exports.handler
// @see https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/nodejs-prog-model-handler.html
//
// Lambda関数のハンドラーの引数
// event: Lambdaが受信したイベントオブジェクト
// context: Lambda実行中のランタイム情報オブジェクト(関数の残り実行時間とか) @see https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/nodejs-prog-model-context.html
// callback: 明示的に呼び出し元にcallbackする場合のメソッド
exports.handler = (event, context, callback) => {
// API Gatewayから認証情報が入力eventに入っているっぽい
// @see https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html
if (!event.requestContext.authorizer) {
// 認証情報が無かったらerrorResponse()をコールして終了
errorResponse('Authorization not configured', context.awsRequestId, callback);
return;
}
// ランダムな文字列からrideIdを生成
const rideId = toUrlString(randomBytes(16));
// rideIdをログ出力
console.log('Received event (', rideId, '): ', event);
// Because we're using a Cognito User Pools authorizer, all of the claims
// included in the authentication token are provided in the request context.
// 私達はCognitoユーザープール認証を使っているため、認証トークンに含まれるclaimsの全てはrequestコンテキストで提供される
// This includes the username as well as other attributes.
// これはユーザ名や他の属性も含んでいる
//
// →つまり、Lambdaに渡される認証トークンでCognitoユーザプールのユーザ情報が取れる
const username = event.requestContext.authorizer.claims['cognito:username'];
// The body field of the event in a proxy integration is a raw string.
// プロキシ統合(API Gateway?)のイベントのフィールドのBodyは、生の文字列です
//
// In order to extract meaningful values, we need to first parse this string
// into an object.
// 意味のある値を抽出するには、まず。この文字列を解析してオブジェクトにする必要がある。
//
// A more robust implementation might inspect the Content-Type
// header first and use a different parsing strategy based on that value.
// より堅牢な実装では最初にContent-Typeヘッダを解析して、その値に寄って異なる方法の解析ストラテジを使用します
//
// →API Gatwayから渡される値の解析方法について書かれているっぽい
// イベントの本文をパースする
const requestBody = JSON.parse(event.body);
// Mapをクリックした地点から緯度経度を取得
const pickupLocation = requestBody.PickupLocation;
// 移動してくるUnicornを取得
const unicorn = findUnicorn(pickupLocation);
// recordRide()を実行
recordRide(rideId, username, unicorn).then(() => {
// You can use the callback function to provide a return value from your Node.js Lambda functions.
// Node.jsのLambda関数からの値のreturnを提供するためのこのコールバック関数を使うことができる
// The first parameter is used for failed invocations.
// 最初のパラメータは失敗時の呼び出しに使われる
// The second parameter specifies the result data of the invocation.
// 2番めのパラメータは呼び出しの結果データを出力する
// Because this Lambda function is called by an API Gateway proxy integration
// the result object must use the following structure.
// Lambda関数はAPI Gatewayのプロキシ統合から呼び出されるため、結果オブジェクトは次のような構造を使う必要がある
callback(null, {
statusCode: 201,
body: JSON.stringify({
RideId: rideId,
Unicorn: unicorn,
UnicornName: unicorn.Name,
Eta: '30 seconds',
Rider: username,
}),
headers: {
'Access-Control-Allow-Origin': '*',
},
});
}).catch((err) => {
console.error(err);
// If there is an error during processing, catch it and return
// from the Lambda function successfully. Specify a 500 HTTP status
// code and provide an error message in the body. This will provide a
// more meaningful error response to the end client.
errorResponse(err.message, context.awsRequestId, callback)
});
};
function findUnicorn()
// This is where you would implement logic to find the optimal unicorn for
// this ride (possibly invoking another Lambda function as a microservice.)
// For simplicity, we'll just pick a unicorn at random.
function findUnicorn(pickupLocation) {
// ログに緯度経度を出力
console.log('Finding unicorn for ', pickupLocation.Latitude, ', ', pickupLocation.Longitude);
// ランダムに移動するUnicornを選出
return fleet[Math.floor(Math.random() * fleet.length)];
}
function recordRide()
function recordRide(rideId, username, unicorn) {
// DynamoDBにputした結果を返す
return ddb.put({
// Ridesテーブルに
TableName: 'Rides',
// 以下のItemを登録
Item: {
RideId: rideId, // ID
User: username, // ユーザ名
Unicorn: unicorn, // 移動したUnicornオブジェクト
UnicornName: unicorn.Name, // 移動したUnicornの名前
RequestTime: new Date().toISOString(), // requestされた時間
},
}).promise();
}
参考にさせてもらったブログなど
今回の収穫
わからなかったこと