Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 8835

WebSocket ミニマム実装 (TypeScript x Serverless x AWS API Gateway)

$
0
0
なんか古いやり方の記事しかなかったので、自分で調べたことを書き残しておくよ 前提・読者の想定 普段 Serverless x Lambda x Node.js x TypeScript が扱えていること そこの詳しい説明は無い やりたいこと WebSocket サーバーを TypeScript で書くよ AWS API Gateway で serve するよ Serverless でデプロイするよ ローカル環境でも簡単にデバッグしたいよ 以上のことをシンプルに実現したいよ 公式サンプルだとDynamo使ったりしてるけど、Dynamo触りたくないし。。。 メリット AWS でオートスケールする WebSocket サーバーが書けるよ とってもお手軽だし、そのうえランニングコストも低い (比較対象: GCP CloudRun しか見てないけど。) やったこと 準備 # in your lambda project npm i aws-lambda-ws-server # for testing websocket npm i -g wscat 他のドキュメントでよく見る serverless-websockets-plugin は 本日時点で不要 aws-lambda-ws-server メリット ローカル起動と、Serverless でのデプロイがサポートされているよ ちゃんと動いたし、中身がシンプルでバグる余地も少なそうだから採用したよ デメリット Starが少ないよ README がいろいろ説明不足だよ 型定義もないよ 😇 注意点 MAPPING_KEY 環境変数で route を分岐するために使うプロパティ名の指定が必要 ローカル起動のデフォルトでは (message json).message が参照される (なんでやねん) デプロイ後のデフォルトは (message json).action このサンプルでは環境変数を使ってローカルの挙動を action に合わせている src/handler.ts const ws = require("aws-lambda-ws-server"); export const websocketApp = ws( ws.handler({ // 接続時に通る async connect(event: WebSocketEvent) { console.log("connection %s", event.id); return { statusCode: 200 }; }, // 切断時に通る async disconnect(event: WebSocketEvent) { console.log("disconnect %s", event.id); return { statusCode: 200 }; }, // 未定義の action を指定すると通る async default(event: WebSocketEvent) { const { id: connectionId, message: { body }, context: { postToConnection }, } = event; console.log("default message", connectionId, body); await postToConnection({ action: "default", echo: body }, connectionId); return { statusCode: 200 }; }, // "myAction1" アクションのハンドラ async myAction1(event: WebSocketEvent) { const { id: connectionId, message: { body }, context: { postToConnection }, } = event; console.log("myAction1", connectionId, body); await postToConnection({ action: "myAction1", echo: body }, connectionId); return { statusCode: 200 }; }, // "myAction2" アクションのハンドラ async myAction2(event: WebSocketEvent) { // 省略: myAction1 と同等の実装 }, // 動かないサンプル (ローカルでは動くが、デプロイ後は動かない) // なぜなら serverless.yml の route 定義が漏れているから async myAction3(event: WebSocketEvent) { // 省略: myAction1 と同等の実装 }, }) ); /** * aws-lambda-ws-server に型定義がないので書いておく */ export type WebSocketEvent = { id: string; event: { requestContext: { routeKey: string; eventType: "CONNECT" | "DISCONNECT" | "MESSAGE"; connectionId: string; }; multiValueHeaders: { [key: string]: string[] }; body: string; }; context: { postToConnection: (body: any, connectionId: string) => Promise<void>; }; // message は any json だが、このプロジェクトでは常にこの形として定義する message: { action: string; body: any; }; }; serverless.yml ※追記したところだけ抜粋 provider: iam: role: statements: - Effect: Allow Action: - "execute-api:ManageConnections" Resource: - "arn:aws:execute-api:*:*:**/@connections/*" functions: websocketApp: handler: src/handler.websocketApp events: - websocket: route: $connect - websocket: route: $disconnect - websocket: route: $default - websocket: route: myAction1 - websocket: route: myAction2 ローカルデバッグ console 1 (server) # 先述の注意点。route分岐のために使うプロパティ名を環境変数で指定 $ export MAPPING_KEY=action $ npx ts-node-dev --inspect=9229 --clear --respawn src/handler.ts # 以下 client からのリクエストに対するログ出力 connection NB+jzmXmqOtPpnCa8eNVDw== myAction1 NB+jzmXmqOtPpnCa8eNVDw== { hello: 'websocket1' } myAction2 NB+jzmXmqOtPpnCa8eNVDw== { hello: 'websocket2' } myAction3 NB+jzmXmqOtPpnCa8eNVDw== { hello: 'websocket3' } default message NB+jzmXmqOtPpnCa8eNVDw== { hello: 'websocket4' } disconnect NB+jzmXmqOtPpnCa8eNVDw== console 2 (client) $ wscat -c ws://localhost:5000 Connected (press CTRL+C to quit) > {"action":"myAction1","body":{"hello":"websocket1"}} < {"action":"myAction1","echo":{"hello":"websocket1"}} > {"action":"myAction2","body":{"hello":"websocket2"}} < {"action":"myAction2","echo":{"hello":"websocket2"}} > {"action":"myAction3","body":{"hello":"websocket3"}} < {"action":"myAction3","echo":{"hello":"websocket3"}} > {"action":"myAction4","body":{"hello":"websocket4"}} < {"action":"default","echo":{"hello":"websocket4"}} デプロイ後の挙動 & wscat -c wss://{****}.execute-api.ap-northeast-1.amazonaws.com/{****} Connected (press CTRL+C to quit) > {"action":"myAction1","body":{"hello":"websocket1"}} < {"action":"myAction1","echo":{"hello":"websocket1"}} > {"action":"myAction2","body":{"hello":"websocket2"}} < {"action":"myAction2","echo":{"hello":"websocket2"}} # ⭐ serverless.yml に "myAction3" route 定義がないため action が認識されていない > {"action":"myAction3","body":{"hello":"websocket3"}} < {"action":"default","echo":{"hello":"websocket3"}} > {"action":"myAction4","body":{"hello":"websocket4"}} < {"action":"default","echo":{"hello":"websocket4"}}

Viewing all articles
Browse latest Browse all 8835

Trending Articles