API Gateway の WebSocket API の作り方 ── Lambda と DynamoDB でコメントをリアルタイムに届ける

目次

概要

チャットやコメント欄のように、誰かが投稿した内容を、ほかのユーザーの画面へ「ほぼリアルタイム」に届けたいことがあります。ふつうの REST API は、クライアントが聞いたときにだけサーバーが答える一方通行のやり取りなので、新しいコメントを知るにはクライアントが何度も問い合わせる(ポーリングする)しかありません。

Amazon API Gateway の WebSocket API を使うと、クライアントとバックエンドの間に接続を張りっぱなしにして、サーバーからクライアントへメッセージを送ることができます。同じ要件は、GraphQL のサブスクリプションを持つ AWS AppSync でも実現できます。

この記事では、WebSocket API の仕組み(ルート・接続 ID・@connections API)と、AppSync との使い分けを整理します。実践では、WebSocket API を作って Lambda と DynamoDB につなぎ、2 つのクライアントの片方から送ったコメントがもう片方に届くことを確かめます。

WebSocket API で接続を張ったまま、Lambda が接続中の全員へコメントを送る
WebSocket API で接続を張ったまま、Lambda が接続中の全員へコメントを送る
クラウドおさる

「新しい知らせはあるか」と何度も長老の家まで走るのは疲れるでござる。のろし台(WebSocket)を立てておけば、知らせのほうから飛んでくるのでござる。今日はそののろし台を建てるでござるよ。

この記事のメリット

  • REST API と WebSocket API の違い(一方向か双方向か)を説明できるようになる
  • WebSocket API のルート($connect・$disconnect・$default・カスタムルート)とルート選択式を理解できる
  • 接続 ID を保存し、@connections API でクライアントへメッセージを送る仕組みを作れるようになる
  • リアルタイム通信で API Gateway の WebSocket API と AppSync を使い分けられるようになる

執筆者とキャラクター紹介

執筆者:土肥(とひ)

株式会社ジェニュインの代表であり元エンジニア。これまでオンプレミスと AWS 環境のインフラ業務を担当。AWS 認定資格の学習で得た知識を、ハンズオンとともに解説するのが得意。直近では新人の教育も行っており、これから技術を学ぶ方が納得できる解説を心がけています。基礎編・Cloud Practitioner の記事では、解説役としても登場します。

土肥

クラウドおさる

クラウドおさる

はじめましてでござる。弊猿はクラウドおさる。IT インフラが異常に発達した猿山から、雲に乗って地上の AWS を学びに降りてきたのでござる。

クラウドおさる

基礎編や Cloud Practitioner の記事では、とひさんに素朴な疑問をぶつける役でござるな。Security - Specialty などの記事では、要所でワンポイントや注意点をひとこと添えるでござる。

クラウドおさる

猿山にもデータセンターや監視当番があって、地上で起きることはだいたい猿山にも同じ仕組みがあるのでござる。例え話がつい猿山に寄るのは、弊猿の癖でござる。

技術解説

REST API と WebSocket API

項目REST APIWebSocket API
通信の向きクライアント → サーバー(聞かれたら答える)双方向(サーバーからも送れる)
接続リクエストごと接続を張ったまま(ステートフル)
新しいデータの受け取り方クライアントが繰り返し問い合わせるサーバーが接続中のクライアントへ送る
向いている用途一般的な CRUDチャット、コメント、通知、ダッシュボードの即時更新

WebSocket API はステートフルなフロントエンドとして、Lambda や DynamoDB などのバックエンドを、クライアントから受け取ったメッセージの内容に応じて呼び出します。

ルートとルート選択式

WebSocket API では、受け取ったメッセージをルートに振り分けて、ルートごとに統合先(Lambda など)を決めます。

ルート呼ばれるとき
$connectクライアントが接続したとき
$disconnectクライアントが切断したとき(切断後に呼ばれる)
$defaultどのルートにも当たらないメッセージを受け取ったとき
カスタムルート(例: sendcomment)ルート選択式の値がルートキーと一致したとき

どのルートに振り分けるかはルート選択式で決めます。$request.body.action とすると、メッセージ(JSON)の action の値がルートキーになります。たとえば {"action": "sendcomment", "comment": "こんにちは"} は sendcomment ルートに入ります。

ルート選択式 $request.body.action でメッセージをルートに振り分ける
ルート選択式 $request.body.action でメッセージをルートに振り分ける

接続 ID と @connections API

クライアントが接続すると、API Gateway はその接続に接続 ID を割り当てます。バックエンドからクライアントへメッセージを送るには、接続 ID を指定して @connections API(POST https://{api-id}.execute-api.{region}.amazonaws.com/{stage}/@connections/{connection_id})を呼びます。SDK では ApiGatewayManagementApi の PostToConnection です。

そのため、よくある作りは次のとおりです。

  1. $connect で接続 ID を DynamoDB に保存する
  2. $disconnect で接続 ID を削除する
  3. コメントを受け取ったら、保存している接続 ID の全員に PostToConnection で送る

PostToConnection を呼ぶ側には、execute-api:ManageConnections の権限が必要です。すでに切れた接続 ID に送ると GoneException(410)が返るので、そのときは保存している一覧から消します。

クラウドおさる

のろし台は、だれが見張っているか(接続 ID)を帳面に付けておかないと、知らせを送る先が分からないのでござる。来たら書き、帰ったら消す。これが肝でござるな。

AppSync との使い分け

項目API Gateway の WebSocket APIAWS AppSync
API の形任意の JSON メッセージGraphQL(クエリ・ミューテーション・サブスクリプション)
リアルタイム配信接続 ID を自分で管理し、@connections で送るサブスクリプションで、データの変更をクライアントへ自動で配信
向いている場面独自のプロトコルやメッセージで双方向通信したい複数のデータソースをまとめて扱い、変更をリアルタイムに反映したい

API Gateway には GraphQL のエンドポイントを作る機能はありません。GraphQL でリアルタイム更新をしたいなら AppSync を選びます。

クラウドおさる

「リアルタイム」と聞いて REST API のポーリングや、長く張りっぱなしの TCP 接続を自作する案に飛びつかないことでござる。WebSocket API か AppSync、この 2 つが本命でござるぞ。

新卒・未経験の方へ
クラウドおさる

AWS を仕事にしたい方へ ── 新卒・未経験から

この記事を書いているのは、AWS の設計・構築を仕事にしている現役エンジニアです。運営元の株式会社ジェニュインでは、新卒・未経験の方も募集しています。異業種や文系からインフラエンジニアになり活躍しているメンバーもいます。

  • ✓システム開発・WEB エンジニア / インフラ構築などの職種で、未経験から応募できます
  • ✓未経験から入社した社員の声を含む社員アンケートを掲載
  • ✓応募フォームから 1 分で応募できます

実践

ハンズオンガイド6 ステップ画面 5 枚

コンソールの横に開いて、チェックしながら進めましょう。

ガイドを開く

別タブで開きます。チェックはこのブラウザに保存されます。

WebSocket API dva-comments-ws を作り、$connect・$disconnect・sendcomment の 3 つのルートを、用意済みの Lambda 関数 dva-ws-handler につなぎます。CloudShell で 2 つのクライアントを接続し、片方から送ったコメントが両方に届くことを確かめます。

前提条件

  • リージョン: ap-northeast-1(東京)
  • 以下のリソースが作成済みであること

まだ用意していない場合は、「作り方の参考」の記事の手順で作れます。リソース名は、この記事の名前に読み替えてください。

リソース種別リソース名内容作り方の参考
DynamoDB テーブルdva-ws-connectionsパーティションキー connectionId(文字列)Amazon DynamoDB でテーブルを作り Lambda から読み書きする
Lambda 関数dva-ws-handlerPython 3.13。event["requestContext"]["routeKey"] でルートを見分け、$connect で接続 ID を保存、$disconnect で削除、sendcomment で全接続に PostToConnection で送るAWS Lambda で関数を作成しイベント駆動で実行する
IAM ロールdva-ws-handler-role上の関数の実行ロール。ログ出力、テーブルの PutItem・DeleteItem・Scan、execute-api:ManageConnections を許可―

作成するリソース一覧

リソース種別リソース名用途
WebSocket APIdva-comments-wsコメントを双方向でやり取りする API
ステージproductionAPI の公開先

手順 1: WebSocket API を作る

AWS マネジメントコンソールにログインし、リージョンが ap-northeast-1(東京)であることを確認します。上部の検索バーに API Gateway と入力し、「API Gateway」を選択します。

「API を作成」をクリックし、「WebSocket API」の「構築」をクリックします。以下を設定して「次へ」をクリックします。

  • API 名: dva-comments-ws
  • ルート選択式: request.body.action(入力欄の前に $ が付いて $request.body.action になります)
  • その他の項目(IP アドレスのタイプなど)はデフォルトのままにします
WebSocket API の詳細の入力画面(API 名とルート選択式)
WebSocket API の詳細の入力画面(API 名とルート選択式)

「ルートを追加」の画面で、「$connect ルートを追加」と「$disconnect ルートを追加」をクリックします(ボタンがルートキーの欄に変わります)。続けて「カスタムルートを追加」をクリックし、ルートキーに sendcomment と入力して「次へ」をクリックします。

ルートの追加画面($connect・$disconnect・sendcomment)
ルートの追加画面($connect・$disconnect・sendcomment)

「統合をアタッチ」の画面で、3 つのルートすべてについて、統合タイプ「Lambda」を選び、Lambda 関数に dva-ws-handler を指定して(一覧から選ぶか、関数の ARN を入力します)「次へ」をクリックします。

「ステージを追加」の画面で、ステージ名が production(デフォルト)になっていることを確認して「次へ」をクリックします。確認画面で「Create and deploy」をクリックすると、API が作成されて production ステージにデプロイされます。

手順 2: 接続先の URL を確かめる

作成した dva-comments-ws の左側ナビゲーションで「ステージ」を選択し、production を選択します。「WebSocket URL」(wss:// で始まる URL)と「@connections URL」(https:// で始まり /@connections で終わる URL)が表示されます。WebSocket URL をコピーしておきます。

ステージ production の画面(WebSocket URL と @connections URL)
ステージ production の画面(WebSocket URL と @connections URL)

コンソールで Lambda 統合を作ると、API Gateway から関数を呼び出す許可が関数のリソースベースポリシーに自動で追加されます。Lambda コンソールで dva-ws-handler の「設定」タブ →「アクセス許可」を開くと、「リソースベースのポリシーステートメント」に、プリンシパルが apigateway.amazonaws.com のステートメントが追加されていることを確認できます。

手順 3: 2 つのクライアントで接続する

コンソール上部の CloudShell のアイコンをクリックして、CloudShell を開きます。次のコマンドで、1 つ目のクライアントとして接続します(初回は wscat のダウンロードの確認が表示されるので y を入力します)。

npx wscat -c wss://<api-id>.execute-api.ap-northeast-1.amazonaws.com/production

Connected (press CTRL+C to quit) と表示されれば接続できています。CloudShell の右上の「アクション」→「列に分割」で画面を左右に分け、右側でも同じコマンドを実行してもう 1 つ接続します。

DynamoDB のコンソールの左側ナビゲーションで「項目を探索」を選択し、テーブルの一覧から dva-ws-connections を選ぶと、スキャンの結果に接続 ID が 2 件保存されていることを確認できます。

dva-ws-connections に接続 ID が 2 件保存されている状態
dva-ws-connections に接続 ID が 2 件保存されている状態

手順 4: コメントを送って両方に届くことを確かめる

左側(1 つ目)のクライアントで、次のメッセージを入力して Enter キーを押します。

{"action": "sendcomment", "comment": "猿山にバナナが届きました"}

メッセージの action が sendcomment なので、sendcomment ルートの Lambda が呼ばれ、保存されている接続 ID の全員にコメントを送ります。左右の両方に、< {"from": "xxxx", "comment": "猿山にバナナが届きました"} のように受信したコメントが表示されます(from は送り主の接続 ID の末尾 4 文字です)。右側のクライアントは何も問い合わせていないのに、サーバーから届いています。

左右 2 つの CloudShell に、送ったコメントが届いている状態
左右 2 つの CloudShell に、送ったコメントが届いている状態

試しに {"action": "unknown"} を送ると、どのルートにも当たらないので、{"message": "Forbidden", …} というエラーのメッセージが返ります(今回は $default ルートを作っていないため)。

左側のクライアントで Ctrl + C を押して切断すると、$disconnect ルートの Lambda が呼ばれ、DynamoDB の接続 ID が 1 件に減ります。

クラウドおさる

聞かれてもいないのに 右側へ知らせが飛んできたでござる。来たら帳面に書き、帰ったら消し、知らせは帳面の全員へ。のろし台の仕組みがそろったのでござるな。

まとめ

要件選ぶもの
独自のメッセージで、サーバーからクライアントへもリアルタイムに送りたいAPI Gateway の WebSocket API
GraphQL で、データの変更をクライアントへリアルタイムに反映したいAWS AppSync(サブスクリプション)
聞かれたら答えるだけでよいAPI Gateway の REST API / HTTP API
  • WebSocket API は接続を張ったまま双方向に通信できる。REST API はリクエストごとの一方向
  • メッセージはルート選択式(例: $request.body.action)でルートに振り分ける。$connect・$disconnect・$default は特別なルート
  • サーバーからクライアントへ送るには、接続 ID を指定して @connections API(PostToConnection)を呼ぶ。権限は execute-api:ManageConnections
  • 接続 ID は $connect で保存し、$disconnect で消す。切れた接続に送ると GoneException になる
  • API Gateway に GraphQL のエンドポイントは無い。GraphQL でのリアルタイム更新は AppSync

参照先

キャリア相談
クラウドおさる

独学の先に、AWS の仕事がある ── ジュニアおさるのキャリア相談

CLF には受かったけれど、次に何をすればいいのか分からない。独学で AWS を学ぶジュニアおさるの相談に、株式会社ジェニュイン代表の土肥が答えています。

  • ✓資格はゴールか、CLF の次に何から手を付けるか
  • ✓EC2 の次に作ってみる構成と、IaC・コスト管理
  • ✓実務経験ゼロでも、面接で話せる「これまで」の作り方
新卒・未経験の方へ
クラウドおさる

AWS を仕事にしたい方へ ── 新卒・未経験から

この記事を書いているのは、AWS の設計・構築を仕事にしている現役エンジニアです。運営元の株式会社ジェニュインでは、新卒・未経験の方も募集しています。異業種や文系からインフラエンジニアになり活躍しているメンバーもいます。

  • ✓システム開発・WEB エンジニア / インフラ構築などの職種で、未経験から応募できます
  • ✓未経験から入社した社員の声を含む社員アンケートを掲載
  • ✓応募フォームから 1 分で応募できます

この記事が気に入ったら
フォローしてね!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

株式会社ジェニュイン(https://genuine-pt.jp)の代表取締役.

2018年〜インフラエンジニアとしてキャリアをスタートし、オンプレミスのネットワーク・サーバ環境で3年半、クラウド環境で4年半の8年間エンジニアとして従事。
2021年に佐藤氏の創業した株式会社Luxyを引き継ぎ、代表に就任。
その後アガルートグループ内の株式会社ジェニュインと合併。
合併後同社代表に就任。

目次