websocket-engineer について
websocket-engineerは、WebSocketシステムを設計・実装する開発者向けのスキルです。接続処理、認証、ルーム、スケーリング、検証、監視を扱い、Socket.IOなどの技術に関する手順と例を提供します。
主な機能
- 01接続規模とレイテンシ要件の分析
- 02認証、ルーム、イベントの設計
- 03Redis pub/subとスティッキーセッションによる拡張
- 04ローカル検証と負荷テストの計画
- 05接続数とエラーの監視
活用シーン
- 01WebSocketサーバー構成の計画
- 02認証付きルームメッセージングの実装
- 03複数サーバーインスタンスへの拡張
- 04デプロイ前の接続動作確認
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ jeffallan/ claude-skills --skill websocket-engineerインストール後、Claude Code で/websocket-engineerを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/alternatives.mdpatterns.mdprotocol.mdscaling.mdsecurity.md詳細
WebSocket エンジニア
コアワークフロー
- 要件を分析する — 接続規模、メッセージ量、レイテンシの要件を特定します
- アーキテクチャを設計する — クラスタリング、パブリッシュ/サブスクライブ、状態管理、フェイルオーバーを計画します
- 実装する — 認証、ルーム、イベントを備えた WebSocket サーバーを構築します
- ローカルで検証する — スケールする前に、接続処理、認証、ルームの動作をテストします(例:
npx wscat -c ws://localhost:3000)。トークンがない場合や無効な場合に認証が拒否されること、ルームへの参加/退出イベント、メッセージ配信を確認します - スケールする — アダプターを有効にする前に Redis 接続とパブリッシュ/サブスクライブの往復を検証します。スティッキーセッションを設定し、複数インスタンス間のテスト接続で動作を確認してから、ロードバランシングを設定します
- 監視する — 接続数、レイテンシ、スループット、エラー率を追跡し、接続数の急増やエラー率のしきい値に対するアラートを追加します
リファレンスガイド
状況に応じて詳細なガイダンスを読み込みます。
| トピック | リファレンス | 読み込むタイミング |
|---|---|---|
| プロトコル | references/protocol.md | WebSocket ハンドシェイク、フレーム、ping/pong、クローズコード |
| スケーリング | references/scaling.md | 水平スケーリング、Redis パブリッシュ/サブスクライブ、スティッキーセッション |
| パターン | references/patterns.md | ルーム、名前空間、ブロードキャスト、確認応答 |
| セキュリティ | references/security.md | 認証、認可、レート制限、CORS |
| 代替手段 | references/alternatives.md | SSE、ロングポーリング、WebSocket を選択する状況 |
コード例
サーバーのセットアップ(認証とルーム管理を備えた Socket.IO)
import { createServer } from "http";
import { Server } from "socket.io";
import { createAdapter } from "@socket.io/redis-adapter";
import { createClient } from "redis";
import jwt from "jsonwebtoken";
const httpServer = createServer();
const io = new Server(httpServer, {
cors: { origin: process.env.ALLOWED_ORIGIN, credentials: true },
pingTimeout: 20000,
pingInterval: 25000,
});
// Authentication middleware — runs before connection is established
io.use((socket, next) => {
const token = socket.handshake.auth.token;
if (!token) return next(new Error("Authentication required"));
try {
socket.data.user = jwt.verify(token, process.env.JWT_SECRET);
next();
} catch {
next(new Error("Invalid token"));
}
});
// Redis adapter for horizontal scaling
const pubClient = createClient({ url: process.env.REDIS_URL });
const subClient = pubClient.duplicate();
await Promise.all([pubClient.connect(), subClient.connect()]);
io.adapter(createAdapter(pubClient, subClient));
io.on("connection", (socket) => {
const { userId } = socket.data.user;
console.log(`connected: ${userId} (${socket.id})`);
// Presence: mark user online
pubClient.hSet("presence", userId, socket.id);
socket.on("join-room", (roomId) => {
socket.join(roomId);
socket.to(roomId).emit("user-joined", { userId });
});
socket.on("message", ({ roomId, text }) => {
io.to(roomId).emit("message", { userId, text, ts: Date.now() });
});
socket.on("disconnect", () => {
pubClient.hDel("presence", userId);
console.log(`disconnected: ${userId}`);
});
});
httpServer.listen(3000);
指数バックオフによるクライアント側の再接続
import { io } from "socket.io-client";
const socket = io("wss://api.example.com", {
auth: { token: getAuthToken() },
reconnection: true,
reconnectionAttempts: 10,
reconnectionDelay: 1000, // initial delay (ms)
reconnectionDelayMax: 30000, // cap at 30 s
randomizationFactor: 0.5, // jitter to avoid thundering herd
});
// Queue messages while disconnected
let messageQueue = [];
socket.on("connect", () => {
console.log("connected:", socket.id);
// Flush queued messages
messageQueue.forEach((msg) => socket.emit("message", msg));
messageQueue = [];
});
socket.on("disconnect", (reason) => {
console.warn("disconnected:", reason);
if (reason === "io server disconnect") socket.connect(); // manual reconnect
});
socket.on("connect_error", (err) => {
console.error("connection error:", err.message);
});
function sendMessage(roomId, text) {
const msg = { roomId, text };
if (socket.connected) {
socket.emit("message", msg);
} else {
messageQueue.push(msg); // buffer until reconnected
}
}
制約事項
必ず行うこと
- ロードバランシングにはスティッキーセッションを使用します(WebSocket 接続はステートフルであり、リクエストは同じサーバーインスタンスにルーティングする必要があります)
- デッド接続を検出するため、ハートビート/ping-pong を実装します(TCP キープアライブだけでは不十分です)
- アプリケーションロジックでフィルタリングするのではなく、メッセージの範囲を限定するためにルーム/名前空間を使用します
- 接続が切断されている間はメッセージをキューに入れ、気付かないままデータが失われるのを防ぎます
- 水平スケーリングの前に、インスタンスあたりの接続数の上限を計画します
絶対に行わないこと
- クラスタリング戦略なしに大量の状態をメモリに保存します(Redis または外部ストアを使用してください)
- 明示的なアップグレード処理なしに、同じポートで WebSocket と HTTP を混在させます
- 接続のクリーンアップ(プレゼンス記録、ルームメンバーシップ、実行中のタイマー)の処理を忘れます
- 本番環境への導入前に負荷テストを省略します — 接続数の急増は HTTP トラフィックの急増とは異なる動作をします
出力テンプレート
WebSocket 機能を実装する場合は、次の内容を提示します。
- サーバーのセットアップ(Socket.IO/ws の設定)
- イベントハンドラー(接続、メッセージ、切断)
- クライアントライブラリ(接続、イベント、再接続)
- スケーリング戦略の簡単な説明
ナレッジリファレンス
Socket.IO、ws、uWebSockets.js、Redis アダプター、スティッキーセッション、nginx WebSocket プロキシ、WebSocket 上の JWT、ルーム/名前空間、確認応答、バイナリデータ、圧縮、ハートビート、バックプレッシャー、水平 Pod オートスケーリング
Synergetic Solutions のプリンシパルコンサルタント、@jeffallan が管理しています。
コメント
Markdown 対応