LoginSignup
2
1

WebSocket APIをAmazon API Gatewayで実装してみる

Posted at

概要

こんにちは。ジャパン・グローバルゲートウェイの中谷です。
現在API Gatewayの勉強をしているのですが、やはり机上の知識だけでは挙動を把握しづらいところがあるので、WebSocket APIを実装してみます。

この記事はAWSの以下のチュートリアルの実施内容をまとめたものです。

Tutorial: Building a serverless chat app with a WebSocket API, Lambda and DynamoDB - Amazon API Gateway
https://docs.aws.amazon.com/apigateway/latest/developerguide/websocket-api-chat-app.html

システム構成

このチュートリアルでは以下のような典型的なサーバーレスアーキテクチャでWebSocketを実装します。

image.png

LambdaとDynamoDBの作成に関しては、チュートリアル内でstarter.ymlというCfnのテンプレートが用意されているので、こちらをマネジメントコンソールからデプロイします。スタック名はwebsocket-api-chat-app-tutorialとします。

image.png

WebSocketAPIの作成

次に、今回のメインであるWebSocketAPIを作成します。
まず、ルート選択式でrequest.body.actionを選択します。ルート選択式では、クライアントがメッセージを送信する際にAPI Gatewayが呼び出すルートを決定します。

image.png

次にルートを追加します。
ルートって何やねんという話ですが、こちらはWebSocket APIで定義されるエンドポイントのことです。これらのエンドポイントは、クライアントから送信されたメッセージを処理する時に使用します。
各ルートでは、クライアントから送信されたメッセージの内容に基づいて、特定のアクション(Lambda関数など)を実行します。これにより、APIは動的にメッセージを処理し、適切なレスポンスを返すことができます。

API GatewayのWebSocket APIでは、$connect$disconnect$defaultの3つのルートが事前に定義されています。

$connectルートでは、クライアントがAPIに接続したときに実行されるアクションを処理します。このルートで、接続の初期化や認証などの処理を行います。

$disconnectルートでは、クライアントがAPIから切断されたときに実行されるアクションを処理します。このルートで、リソースの解放や接続情報の削除などの処理を行います。

$defaultルートは、他のルートにマッチしないメッセージを処理する際のルートです。このルートで、エラー処理や、未定義のメッセージタイプの処理を行うことができます。

また、これらのルートの他にも、自身でカスタムルートを定義して、特定のメッセージタイプやアクションを処理することもできます。今回のチュートリアルでは、sendmessageルートを定義して、リアルタイムにメッセージを送受信できるようにします。

image.png

ルートを追加したら、各ルートに対応するLambda関数を紐づけます。これにより、ルート選択式で評価したルートごとにハンドラー内に記述した処理が実施されます。

image.png

最後に、ステージを追加してデプロイします。

image.png

sendmessageハンドラーの処理内容

Cfnのテンプレートを見ると、yaml内にハードコーディングしたソースから動的にLambda関数のハンドラーを生成しているようなので、今回追加したsendmessageルートで使用するハンドラーのコード部分を抜粋してみます。

index.js
const AWS = require('aws-sdk');
const ddb = new AWS.DynamoDB.DocumentClient();

exports.handler = async function (event, context) {
  let connections;
  try {
    connections = await ddb.scan({ TableName: process.env.table }).promise();
  } catch (err) {
    return {
      statusCode: 500,
    };
  }
  const callbackAPI = new AWS.ApiGatewayManagementApi({
    apiVersion: '2018-11-29',
    endpoint:
      event.requestContext.domainName + '/' + event.requestContext.stage,
  });

  const message = JSON.parse(event.body).message;

  const sendMessages = connections.Items.map(async ({ connectionId }) => {
    if (connectionId !== event.requestContext.connectionId) {
      try {
        await callbackAPI
          .postToConnection({ ConnectionId: connectionId, Data: message })
          .promise();
      } catch (e) {
        console.log(e);
      }
    }
  });

  try {
    await Promise.all(sendMessages);
  } catch (e) {
    console.log(e);
    return {
      statusCode: 500,
    };
  }

  return { statusCode: 200 };
};

色々書いてありますが、メインの処理としては

  • DynamoDBから接続情報を格納しているテーブルをスキャンし、すべての接続を取得
  • ハンドラの引数eventにクライアントから送信されたペイロードを格納し、その後ペイロードのbody部からmessage要素を抽出してJSONでパース
  • 接続元の全てのクライアントに対して受信したメッセージを送信し、エラーが発生した場合にはステータスコード500を返し、正常に処理が完了した場合は200を返却

といった内容になっています。

wscatのインストール

wscatを使用すると、WebSocketの動作確認にあたって自身でサーバー/クライアント側のプログラムを記述しなくても動作確認をすることが出来ます。

wscatは以下のコマンドでインストールできます。

npm install -g wscat

wscatでの動作確認

wscatで動作確認するにあたり、WebSocket URLをマネジメントコンソールからコピーします。URLは以下のような形式になっています。
wss://abcdef123.execute-api.us-east-2.amazonaws.com/production

取得したら、以下のコマンドで接続します。

wscat -c wss://abcdef123.execute-api.us-west-2.amazonaws.com/production

今回は二つのターミナル上から実行し、メッセージを送受信できるか確認します。
各ターミナル上で以下のような形式でメッセージを送信します。actionsendmessageと入れることで、ルート選択式での評価後sendmessageルートが呼び出されます。

{"action": "sendmessage", "message": "hogehoge"}

ターミナルの出力を確認すると、お互いに送信したメッセージが表示されていて、ちゃんとメッセージがやり取りされていることが確認出来ます。

image.png

まとめ

WebSocket APIはなかなか普段関わる機会がないのでイメージがしづらかったですが、自分で手を動かすと理解度が高まった気がします。今度はGraphQLにも挑戦してみたいです。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1