👻

「actix-web」入門#6 「websockets」の「chat」使ってみた

2022/06/19に公開

今回は「actix-web」のexampleにある「websockets」の「chat」を使ってみます。
見たかったのは独自「session」の取り扱いです。

目次は以下の通りです。

  1. WebSocketsとは
  2. APIとWebSocketsとWebHooksの違い
  3. 「websockets」の「chat」を実行してみる
  4. 「websockets」の「chat」におけるソースの確認
  5. 次回

今回は以下のソースを利用して、「actix-web」の「websockets」を使ってみます。
https://github.com/actix/examples/tree/ec919b2424034a1ed468c0226b316c72c69d8fdb/websockets

1. WebSocketsとは

以下、MozillaのMDNを確認してみます。
https://developer.mozilla.org/ja/docs/Web/API/WebSockets_API
「ユーザーのブラウザーとサーバー間で対話的な通信セッションを開く」とある通り、一般的な対話式Webサービスと考えてよいようです。

WebSocketはインターフェースとして定義されており、データを送受信する機能のことのようです。

2. APIとWebSocketsとWebHooksの違い

ちょうどいい機会なので、APIとWebSocketsとWebHooksの違いを確認してみます。

以下サイトを参考にしました。
https://blog.bitsrc.io/apis-vs-websockets-vs-webhooks-what-to-choose-5942b73aeb9b

APIは、エンドユーザ(ブラウザ)とWebサーバをつなげます。
ただ、APIはWebSocketsとの間で、情報を保持するための機能に乏しく、ポーリングに時間がかかってしまいます。

※ポーリング《ネットワークに接続された複数の端末の回線制御方式の1つ;各端末が受信可能かどうかを順次問う》.

WebSocketsは、APIのポーリングに時間がかかるという欠点を埋めるために生み出された技術と考えてよいでしょう。特徴的なのが、「WebSocketハンドシェイク・リクエスト」という概念です。この概念は、ブラウザとサーバ間の一連の流れを処理するという点でWeb-Sessionの概念と一致する部分があります。
ただ、接続を常に開いたままにしておくと、リソース消費量、電力使用量(モバイルデバイス)が増加し、拡張が困難になります。

WebHookは、WebSocketsにおけるリソース消費を少なくするため、拡張されたと考えてよいでしょう。主にプッシュ通知などで使われます。エンドユーザがWebHook(コールバックURL)をサービスプロバイダーに登録し、そのURLがWebHookからデータを受信する場所として機能します。
イメージとしては、zoomで会議を予約すると特定のIDが生成されますが、それを「コールバックURL」として定義していると考えるとイメージしやすいと思います。

3. 「websockets」の「chat」を実行してみる

とりあえず、「cargo run」します。

ブラウザで確認してみます。

「connect」を押して、テスト的に会話を送信してみます。

テスト的に対話できているようです。

「disconnect」して、再度「connect」してみます。

ちゃんと「Total visitors」もカウントアップされているのが確認できます。

4. 「websockets」の「chat」におけるソースの確認

ソースの構成は以下の通りです。

「main.rs」では、ルートに対して「static/index.html」を適用し、それぞれ画面表示機能は「/」、チャット機能「/ws」、セッションカウント機能「/count」として作成されています。

main.rs
/// もろもろのimport文

...

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    ...

    HttpServer::new(move || {
        App::new()
            .app_data(web::Data::from(app_state.clone()))
            .app_data(web::Data::new(server.clone()))
            .service(web::resource("/").to(index))
            .route("/count", web::get().to(get_count))
            .route("/ws", web::get().to(chat_route))
            .service(Files::new("/static", "./static"))
            .wrap(Logger::default())
    })
    ...
}

「server.rs」では、チャットサービスのサーバが格納されてます。
細かく見ると、messageやらconnectionが定義されています。これらはsessionを管理するためにsessionへのコネクション機能・メッセージ送付機能として定義されています。

server.rs
/// もろもろもimport文

/// Chat server sends this messages to session
#[derive(Message)]
#[rtype(result = "()")]
pub struct Message(pub String);

/// Message for chat server communications

/// New chat session is created
#[derive(Message)]
#[rtype(usize)]
pub struct Connect {
    pub addr: Recipient<Message>,
}

/// Session is disconnected
#[derive(Message)]
#[rtype(result = "()")]
pub struct Disconnect {
    pub id: usize,
}

...

/// Join room, send disconnect message to old room
/// send join message to new room
impl Handler<Join> for ChatServer {
    type Result = ();

    fn handle(&mut self, msg: Join, _: &mut Context<Self>) {
        let Join { id, name } = msg;
        let mut rooms = Vec::new();

        // remove session from all rooms
        for (n, sessions) in &mut self.rooms {
            if sessions.remove(&id) {
                rooms.push(n.to_owned());
            }
        }
        // send message to other users
        for room in rooms {
            self.send_message(&room, "Someone disconnected", 0);
        }

        self.rooms
            .entry(name.clone())
            .or_insert_with(HashSet::new)
            .insert(id);

        self.send_message(&name, "Someone connected", id);
    }
}

「session.rs」では、チャットのためのsessionを管理します。
厳密には「actix_web_actors::ws::WebsocketContext」を使って、sessionを定義するらしい。

5. 次回

exampleの「databases」を触ってみます。

上記以降は以下のいずれかをやっていく感じです。
・test周りとかの解説
・tls周りとかの解説

Discussion