はじめに
本日からAPIGatewayでWebSocket APIを構築することができるようになりました!!
従来、WebSocket通信ができるアプリケーションやAPIを構築するためにはWebsocket通信の接続を管理するホストとなるサーバを立てておく必要がありました。
よくやる構成としては以下のようなものだったのではないでしょうか?
アクセスはALBで負荷分散したいが、EC2にステートを持たせられないのでWebSocket通信の接続情報はElasticCacheなどに退避させておくことでスケールする環境をなんとか作るといった具合でしょうか。
この構成は安定した作りではあるものの、個人的には
の3重苦で、あまり好きではないです。昔苦労したなぁ・・・
そこで待望のAPIGatewayがWebSocketをサポートした(2018年12月19日から)とのことらしいので早速使ってみようと思います。ワクワク🎵
APIGatewayでWebSocketの設定をしてみる
APIGatewayでWebSocket APIを構築することで、双方向通信ができるアプリケーションを簡単に実装することができるようになります!
WebSocket APIを使うことでチャットや双方向動画配信などが可能になり、Webアプリケーションの可能性が広がりますね!!
まずはじめにAPIGatewayのコンソール画面をのぞいてみましょう。
APIの作成から新規にAPIGatwayを作成しようとすると通信プロトコルをRESTにするかWebSocketにするかが選べます。
WebSocketにチェックを入れるとAPI名、Route Selection Expression、説明の入力が求められます。
Route Selection Expressionという項目は初登場ですね。
$request.body.action
のように入力しておきましょう。
クライアントからAPIを送信する際にリクエストメッセージは以下のようなJSON形式で送ります。
{
"service" : "chat",
"action" : "join",
"data" : {
"room" : "room1234"
}
}
serviceはrouteKey、評価された値と正確に一致するルートを使用します。Route Selection Expressionを $request.body.action
として設定しておくと、actionプロパティに基づいてAPIの動作を選択することができます。あとはdataが実際に送られるメッセージデータですね。
ルートとは?
WebSocket APIはルートという単位で構成されています。RESTの場合はリソースパスでしたね。特定のリクエストで使用するルートをどう使うか?という設定を**Route Selection Expression(選択式)**にて行います。式はルートのrouteKeyの値に対応するものに接続されます。
3つの特別な routeKey値 |
説明 |
---|---|
$default | 選択式がAPIルート内の他のrouteKeyに 一致しない値を生成する場合に使う |
$connect | クライアントがWebSocket APIに 最初に接続するときに使う |
$disconnect | クライアントがAPIから切断するときに使う |
詳細はこちらに記載されています。
チャットAPIを作ってみる
今回作成する構成はこちらです。ユーザはOnConnect Lambdaを叩いて接続します。SendMessage Lambdaはユーザからのメッセージ送信を受け取ると、このWebSocket通信で繋がるすべてのユーザにPUSHします。OnDisconnect Lambdaを叩いてWebSocket通信を切断します。接続情報はDynamoDBに保持することでLambdaをステートレスに保ちます。LambdaはAWSからサンプルとして提供されているこちらのServerlessApplicationRepositoryのアプリを使用します。
サンプルアプリケーション(Lambda)のデプロイ
まずはServerlessApplicationRepositoryのアプリを自分のAWS上にデプロイします。
# 今回使用するアプリケーションのリポジトリをクローン
$ git clone https://github.com/aws-samples/simple-websockets-chat-app.git
# クローンしてきたディレクトリに移動
$ cd simple-websockets-chat-app
# SAMテンプレートをpackageコマンドを使用してLambdaのソースコードと共にS3に格納
$ sam package --template-file template.yaml --output-template-file output.yaml --s3-bucket <自分のS3バケット>
# デプロイ
$ aws cloudformation deploy --template-file output.yaml --stack-name <スタック名> --capabilities CAPABILITY_IAM
今回はスタック名 websocket
としてデプロイしました。
DynamoDBは simplechat_connections という名前で出来上がります。
APIGatewayの設定
さて、いよいよAPIGatewayの設定を進めていきます。
構成図どおり、3つのルートを作成します。まずはsendmessageルートから作成しましょう。
New Route Keyにsendmessageを入力して確定します。
あとはこのルートとLambda関数を接続しましょう。
SendMessage Lambda
ここでSendMessage Lambda関数に注目してみます。
ユーザの誰かが “sendmessage”アクションでメッセージを送信すると、
DynamoDBテーブルから、現在接続されているすべてのユーザを検索します。
検索されたすべてのユーザに対して、postToConnectionを呼ぶことでメッセージを送信します。
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: MIT-0
var AWS = require('aws-sdk');
AWS.config.update({ region: process.env.AWS_REGION });
var DDB = new AWS.DynamoDB({ apiVersion: "2012-10-08" });
require('aws-sdk/clients/apigatewaymanagementapi');
exports.handler = function (event, context, callback) {
var scanParams = {
TableName: process.env.TABLE_NAME,
ProjectionExpression: "connectionId"
};
DDB.scan(scanParams, function (err, data) {
if (err) {
callback(null, {
statusCode: 500,
body: JSON.stringify(err)
});
} else {
var apigwManagementApi = new AWS.ApiGatewayManagementApi({
apiVersion: "2018-11-29",
endpoint: event.requestContext.domainName + "/" + event.requestContext.stage
});
var postParams = {
Data: JSON.parse(event.body).data
};
var count = 0;
data.Items.forEach(function (element) {
// ユーザの誰かが “sendmessage”アクションでメッセージを送信すると、
// DynamoDBテーブルから、現在接続されているすべてのユーザを検索します。
// 検索されたすべてのユーザに対して、postToConnectionを呼ぶことでメッセージを送信します。
postParams.ConnectionId = element.connectionId.S;
apigwManagementApi.postToConnection(postParams, function (err) {
if (err) {
if (err.statusCode === 410) {
console.log("Found stale connection, deleting " + postParams.connectionId);
DDB.deleteItem({ TableName: process.env.TABLE_NAME,
Key: { connectionId: { S: postParams.connectionId } } });
} else {
console.log("Failed to post. Error: " + JSON.stringify(err));
}
} else {
count++;
}
});
});
callback(null, {
statusCode: 200,
body: "Data send to " + count + " connection" + (count === 1 ? "" : "s")
});
}
});
};
OnConnect Lambda
次にWebSocket通信を開始するLambdaです。APIGatewayの設定は**$connect**ルートに接続させるようにします。
関数の中身は非常にシンプルです。リクエストメッセージ(requestContext)のconnectionId値をDynamoDBテーブルに保存します。
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: MIT-0
var AWS = require("aws-sdk");
AWS.config.update({ region: process.env.AWS_REGION });
var DDB = new AWS.DynamoDB({ apiVersion: "2012-10-08" });
exports.handler = function (event, context, callback) {
// リクエストメッセージからconnectionIdを取得して
var putParams = {
TableName: process.env.TABLE_NAME,
Item: {
connectionId: { S: event.requestContext.connectionId }
}
};
// DynamoDBテーブルに保存する
DDB.putItem(putParams, function (err) {
callback(null, {
statusCode: err ? 500 : 200,
body: err ? "Failed to connect: " + JSON.stringify(err) : "Connected."
});
});
};
OnDisConnect Lambda
こちらもシンプルですね。OnConnectと真逆の処理になります。DynamoDBに保持してあるデータを削除するだけです。
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: MIT-0
var AWS = require("aws-sdk");
AWS.config.update({ region: process.env.AWS_REGION });
var DDB = new AWS.DynamoDB({ apiVersion: "2012-10-08" });
exports.handler = function (event, context, callback) {
// リクエストメッセージからconnectionIdを取得して
var deleteParams = {
TableName: process.env.TABLE_NAME,
Key: {
connectionId: { S: event.requestContext.connectionId }
}
};
// DynamoDBのテーブルから削除
DDB.deleteItem(deleteParams, function (err) {
callback(null, {
statusCode: err ? 500 : 200,
body: err ? "Failed to disconnect: " + JSON.stringify(err) : "Disconnected."
});
});
};
ステージへのデプロイ
ここは従来の方法と変わらないようです。
Action>Deploy APIを押すと設定画面が立ち上がりますので
デプロイ先のステージ名などを入力します。ここではprodステージにデプロイします。
ステージが正常にデプロイされるとWebSocket URLとConnection URLが発行されます。これで構築作業は完了です。
Websocketを体感する(テストしてみる)
ここまでできたら生成されたWebSocket URLに対してwssコネクションを貼りましょう。
コマンドラインツール の wscat を使って検証してみます。
# wscatコマンドのインストール
$ npm install -g wscat
# APIGateayのステージエンドポイントにwssコネクションを貼る
$ wscat -c wss://{YOUR-API-ID}.execute-api.{YOUR-REGION}.amazonaws.com/prod
connected (press CTRL+C to quit)
# メッセージを送信
> {"action":"sendmessage", "data":"hello world"}
< hello world
双方向通信の様子
コンソールを複数開いてメッセージを送信すると、すべてのコンソールにメッセージがPUSHされていることがわかります。
下の動画ではで真ん中と上のコンソールがwscatコマンドでWebSocket通信を行なっている様子が見て取れます。一番下のコンソールはWebSocket通信を開始すると、DynamoDBにconnectionIdが保存されてWebSocket通信を切断するとconnectionIdが破棄される様子を観測しています。
さいごに
いかがだったでしょうか?今回はAPIGatewayの新機能であるWebSocket通信のサポートを細かく追って解説してみました。
これでフルサーバレスでのWebSocket通信が可能になります。チャットでも双方向動画通信でもゲームでも!活躍する機会はどんどん出てきそうですね!!活用していきましょう