レシピ#8-6: AIと実装するリアルタイム通信 (WebSocket編)

これまでのAPIは、すべてクライアント(アプリ)からのリクエストに応じてサーバーが応答を返す「リクエスト/レスポンス」モデルでした。しかし、チャットアプリのように、サーバー側からクライアントに「新しいメッセージが来たよ!」とプッシュ通知したい場合はどうすれば良いでしょうか?

そのための技術がWebSocketです。WebSocketは、クライアントとサーバーの間に「双方向のトンネル」を開き、どちらからでも、いつでもリアルタイムにメッセージを送り合うことを可能にします。

このレシピでは、Dart Frogを使ってシンプルなWebSocketチャットサーバーを構築し、AIと協力してその仕組みを理解していきます。


Step 1: WebSocketエンドポイントの作成

Dart Frogでは、WebSocketのエンドポイントを作成するのも非常に簡単です。dart_frog_web_socketパッケージのwebSocketHandlerを使い、通常のルートと同じようにonRequest関数からそのハンドラを返すだけです。

🤖 AI活用プロンプト (WebSocketサーバー雛形)

あなたはDart Frogのリアルタイム通信機能に詳しいエキスパートです。

Dart Frogで、routes/ws.dartというパスにWebSocketエンドポイントを作成したい。

要件: 1. クライアントが接続してきたら、「[<接続ID>] connected!」とサーバーのコンソールに表示する。 2. クライアントからメッセージを受け取ったら、そのメッセージをそのまま「ECHO: <受け取ったメッセージ>」として、送ってきたクライアントにだけ送り返す(エコーサーバー)。 3. クライアントが切断したら、「[<接続ID>] disconnected!」とサーバーのコンソールに表示する。

この要件を満たす、ws.dartの完全なコードを生成してください。 WebSocketChannelstream.listenを使ってください。

AIの応答(例):

// routes/ws.dart
import 'package:dart_frog/dart_frog.dart';
import 'package:dart_frog_web_socket/dart_frog_web_socket.dart';

Future<Response> onRequest(RequestContext context) async {
  // WebSocketハンドラを定義
  final handler = webSocketHandler(
    (channel, protocol) {
      // 接続IDを生成(デバッグ用)
      final connectionId = channel.hashCode;
      print('[$connectionId] connected!');

      // クライアントからのメッセージを待ち受ける
      channel.stream.listen(
        (message) {
          print('[$connectionId] received: $message');
          // 受け取ったメッセージをエコーバックする
          channel.sink.add('ECHO: $message');
        },
        // クライアントが切断した時の処理
        onDone: () => print('[$connectionId] disconnected!'),
      );
    },
  );

  // リクエストをWebSocketハンドラにアップグレード
  return handler(context);
}

このファイルを保存し、Dart Frogサーバーを起動すれば、ws://localhost:8080/wsで接続を待ち受ける、シンプルなエコーサーバーが完成します。


Step 2: グローバルなチャットルームの実装

エコーサーバーは基本を学ぶのに最適ですが、実際のチャットアプリでは、あるクライアントからのメッセージを、接続している他のすべてのクライアントにブロードキャスト(一斉配信)する必要があります。

これには、接続されているすべてのWebSocketChannelを管理するための、グローバルな「マネージャー」クラスが必要になります。この設計もAIに任せましょう。

🤖 AI活用プロンプト (チャットルームマネージャー)

Dart Frogで、複数のクライアントが参加できるチャットルームを実装したい。

接続されているすべてのWebSocketChannelを管理し、メッセージをブロードキャストするためのChatRoomManagerクラスを設計・実装してください。

要件: 1. シングルトンパターンで実装し、アプリ全体で唯一のインスタンスを保持する。 2. add(WebSocketChannel channel)メソッド: 新しいクライアントを管理リストに追加する。 3. remove(WebSocketChannel channel)メソッド: 切断したクライアントをリストから削除する。 4. broadcast(String message)メソッド: 管理しているすべてのクライアントに、同じメッセージを一斉配信する。

このプロンプトにより、接続管理の核心となるロジックが生成されます。 次に、このマネージャーをDart Frogのミドルウェアとして提供し、ルートハンドラから使えるようにします。

🤖 AI活用プロンプト (ミドルウェアとルートの実装)

先ほど作成したChatRoomManagerを、Dart Frogのミドルウェアとして提供するコードを書いてください。

そして、routes/ws.dartのハンドラを修正し、 - 新しいクライアントが接続したら、マネージャーに追加する。 - クライアントからメッセージを受け取ったら、マネージャーのbroadcastメソッドを呼び出す。 - クライアントが切断したら、マネージャーから削除する。

というロジックを実装してください。


Step 3: Flutterクライアントからの接続

サーバーの準備ができたので、Flutterアプリ側からWebSocketに接続します。公式のweb_socket_channelパッケージを使うのが簡単です。

🤖 AI活用プロンプト (Flutter WebSocketクライアント)

web_socket_channelパッケージを使って、ws://localhost:8080/wsに接続するFlutterのUIを実装してください。

要件: 1. WebSocketChannelを初期化し、接続する。 2. サーバーからのメッセージを待ち受けるStreamBuilderを使い、受信したメッセージを画面にリスト表示する。 3. TextFieldと送信ボタンを配置し、ボタンが押されたらchannel.sink.add()でサーバーにメッセージを送信する。 4. ウィジェットが破棄される時に、channel.sink.close()で接続を適切に閉じる。

この機能を持つStatefulWidgetの完全なコードを提示してください。


このレシピを通じて、あなたはリクエスト/レスポンスモデルを超えた、リアルタイム双方向通信の基本をマスターしました。チャットアプリ、オンラインゲーム、共同編集ツールなど、インタラクティブなアプリケーションを構築するための扉が開かれたのです。