カテゴリー
【AWSで構築するサーバレスWebSocket①】 AWS APIGatewayからWebSocket APIを試してみる
※ 当ページには【広告/PR】を含む場合があります。
2021/07/22

手動でWebSocket APIを構築する

WebSocket APIルート
ルートセレクションエクスプレッション(ルート選択式)
ルート
routeKey
$default :
RSXがAPIルート内の他のrouteKeyに一致しない場合に使用される。
例としてはエラー処理などを実装するときに利用する
$connect :
クライアントが最初に接続するときの処理に使うルート
$disconnect :
クライアントが切断する処理に使うルート。
実装は任意で、切断処理をカスタマイズする場合に利用
$request.body.action
1. WebSocket API接続時にクライアントを情報をユーザーとして登録
2. 接続後に提供されるコールバックURLを使用して特定のユーザーにメッセージを送信
3. 接続確立後にユーザーは相互にメッセージを送受信する
4. ユーザーが切断後は登録情報を削除する
ルートハンドラの実装
+ hello-websocket-onconnect :
$connectルートに対応。
接続時の初期化処理
+ hello-websocket-ondisconnect :
$disconnectルートに対応。
切断時の後処理
+ hello-websocket-onerror :
$defaultルートに対応。
接続中のエラーハンドリング
+ hello-websocket-onmessage :
カスタムルートに対応。
接続中のクライアント間メッセージを転送

WebSocket APIのアクセス権限

AWSLambdaBasicExecutionRole
AmazonAPIGatewayInvokeFullAccess
execute-api:ManageConnections
$connectイベント用のハンドラ
$connect
$disconnect
$connect
exports.handler = async (event, context, callback) => {
//...何か接続開始処理を行う
try {
return {
statusCode: 200,
body: 'Connected! Hello WebSocket App!',
};
} catch(e) {
return {
statusCode: 500,
body: `Failed to connect: ${JSON.stringify(e)}`,
};
}
};
$disconnectイベント用のハンドラ
$disconnect
exports.handler = async (event, context, callback) => {
//...何か切断処理を行う
try {
return {
statusCode: 200,
body: 'Disconnected! Good-bye WebSocket App!',
};
} catch(e) {
return {
statusCode: 500,
body: `Failed to disconnect: ${JSON.stringify(e)}`,
};
}
};
$defaultイベント用のハンドラ
$connect
$disconnect
exports.handler = async (event, context, callback) => {
//...何かのルート解析処理を行う
return {
statusCode: 200,
body: 'Undefined Route Detected!',
};
};
ユーザー定義イベント用のハンドラ
sendMessageRoute
request.body.action
sendMessageRoute
{
"action": "sendMessageRoute",// ルート変数は必須
"data": "<送信したいデータ(UTF8)>",
"arg1": "<ユーザー定義変数1>",
"arg2": "<ユーザー定義変数2>",
...
}
action
1. WebSocket通信するためApiGatewayManagementApiインスタンスを生成。
コンストラクター引数ではendpointが必要となり、
その際にrequestContextからdomainNameとstageを利用する
2. ハンドラ関数のevent引数に渡されるRequestContextからconnectionIdを取得
3. WebSocketクライアントからのメッセージがforwardが空でなく、
指定先のクライアントのconnectionIdがあった場合には、
メッセージをそのクライアントに転送先を変更する。
forwardが空だった場合には、自分にメッセージを送る
4. ApiGatewayManagementApiインスタンスから、
postToConnectionメソッドで指定のクライアントにメッセージを送信する
const AWS = require('aws-sdk');
AWS.config.update({ region: process.env.AWS_REGION });
require('aws-sdk/clients/apigatewaymanagementapi');
exports.handler = async (event, context, callback) => {
const apigwManagementApi = new AWS.ApiGatewayManagementApi({
apiVersion: "2018-11-29",
endpoint: event.requestContext.domainName + "/" + event.requestContext.stage
});
const forward_id = JSON.parse(event.body).forward;
const ConnectionId = forward_id != null ? forward_id : event.requestContext.connectionId;
const Data = forward_id != null ? `${JSON.parse(event.body).data}` : `YOUR ID: ${ConnectionId}`;
const postParams = { ConnectionId, Data };
apigwManagementApi.postToConnection(postParams, (err) => {
if (err) {
console.log("Failed to post. Error: " + JSON.stringify(err));
}
});
callback(null, {
statusCode: 200,
body: "Send data!"
});
};
API Gatewayインスタンスの作成

wsMyApp
request.body.action






2つのクライアント間での接続確認
wscatを使ってコンソールで確認
$ npm install -g wscat

$ wscat -c wss://[APIのドメインID].[APIのリージョン名].amazonaws.com/[APIをデプロイしたステージ名]
Connected (press CTRL+C to quit)
#👇forwardフィールド無しでsendMessageRouteへシグナル送信
> {"action":"sendMessageRoute", "data":"who am i"}
< YOUR ID: C51DherHNjMCI3Q=
$ wscat -c wss://[APIのドメインID].[APIのリージョン名].amazonaws.com/[APIをデプロイしたステージ名]
Connected (press CTRL+C to quit)
> {"action":"sendMessageRoute", "data":"who am i"}
< YOUR ID: C50a8cvrtjMCI7w=
> {"action":"sendMessageRoute", "data":"Hello wscat2!", "forward":"C50a8cvrtjMCI7w="}
< Hello wscat2!
> {"action":"sendMessageRoute", "data":"Hello wscat1!", "forward":"C51DherHNjMCI3Q="}
< Hello wscat1!

ブラウザで確認
//WebSocket APIでインスタンスを生成
const ws = new WebSocket("wss://[APIのドメインID].[APIのリージョン名].amazonaws.com/[APIをデプロイしたステージ名]")
//メッセージの受信のためのコールバック
ws.onmessage = (e) => {
const data = JSON.parse(e.data);
// ...
}
//定義済みルートへメッセージ送信するメソッド
const message = {
"action": "sendMessageRoute",
"data": "送信メッセージ",
// ...
}
ws.send(JSON.stringify(message))
まとめ
参考サイト
記事を書いた人
ナンデモ系エンジニア
主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。
カテゴリー