中級 api

WebSocket

はじめに

WebSocketは、ブラウザとサーバー間のリアルタイムな双方向通信を実現します。Rust/WasmでWebSocket接続の管理、メッセージのパース、アプリケーション状態の更新を行えます — すべて型安全に。

セットアップ

[dependencies.web-sys]
version = "0.3"
features = [
    "WebSocket",
    "MessageEvent",
    "ErrorEvent",
    "CloseEvent",
    "BinaryType",
    "console",
]

接続

use web_sys::WebSocket;

let ws = WebSocket::new("wss://echo.websocket.org")?;

// バイナリデータ用のバイナリモードを設定
ws.set_binary_type(web_sys::BinaryType::Arraybuffer);

メッセージの送信

// テキストの送信
ws.send_with_str("hello")?;

// バイナリデータの送信
let data: Vec<u8> = vec![1, 2, 3, 4];
let array = js_sys::Uint8Array::new_with_length(data.len() as u32);
array.copy_from(&data);
ws.send_with_array_buffer(&array.buffer())?;

メッセージの受信

use web_sys::MessageEvent;
use wasm_bindgen::closure::Closure;

let onmessage = Closure::wrap(Box::new(move |e: MessageEvent| {
    // テキストメッセージ
    if let Some(text) = e.data().as_string() {
        log!("Text: {}", text);
    }

    // バイナリメッセージ
    if let Ok(buf) = e.data().dyn_into::<js_sys::ArrayBuffer>() {
        let array = js_sys::Uint8Array::new(&buf);
        let data = array.to_vec();
        log!("Binary: {} bytes", data.len());
    }
}) as Box<dyn FnMut(MessageEvent)>);

ws.set_onmessage(Some(onmessage.as_ref().unchecked_ref()));
onmessage.forget();

接続のライフサイクル

// 接続時
let onopen = Closure::wrap(Box::new(move || {
    log!("Connected!");
}) as Box<dyn FnMut()>);
ws.set_onopen(Some(onopen.as_ref().unchecked_ref()));
onopen.forget();

// 切断時
let onclose = Closure::wrap(Box::new(move |e: web_sys::CloseEvent| {
    log!("Disconnected: code={}, reason={}", e.code(), e.reason());
}) as Box<dyn FnMut(_)>);
ws.set_onclose(Some(onclose.as_ref().unchecked_ref()));
onclose.forget();

// エラー時
let onerror = Closure::wrap(Box::new(move |e: ErrorEvent| {
    log!("Error: {}", e.message());
}) as Box<dyn FnMut(_)>);
ws.set_onerror(Some(onerror.as_ref().unchecked_ref()));
onerror.forget();

再接続パターン

#[wasm_bindgen]
pub struct ReconnectingWs {
    url: String,
    ws: Option<WebSocket>,
    retry_count: u32,
}

#[wasm_bindgen]
impl ReconnectingWs {
    pub fn connect(&mut self) -> Result<(), JsValue> {
        let ws = WebSocket::new(&self.url)?;

        let url = self.url.clone();
        let onclose = Closure::wrap(Box::new(move || {
            // 遅延後に再接続
            let window = web_sys::window().unwrap();
            let url = url.clone();
            let closure = Closure::once(move || {
                log!("Reconnecting to {}...", url);
                // 接続を再作成
            });
            window.set_timeout_with_callback_and_timeout_and_arguments_0(
                closure.as_ref().unchecked_ref(), 3000
            ).unwrap();
            closure.forget();
        }) as Box<dyn FnMut()>);

        ws.set_onclose(Some(onclose.as_ref().unchecked_ref()));
        onclose.forget();

        self.ws = Some(ws);
        Ok(())
    }
}

ユースケース

アプリケーション Wasmを使う理由
チャットアプリ Rustでメッセージをパース・検証、型安全なプロトコル
ライブダッシュボード ストリーミングデータ(メトリクス、ログ)を高スループットで処理
マルチプレイヤーゲーム バイナリプロトコルのパース、状態の同期
共同編集エディタ 正確性のためにRustでOT/CRDTアルゴリズムを実装

試してみよう

スターターコードは、接続、送信、受信、エラーハンドリングを行うWebSocketクライアント構造体を示しています。ブラウザのWebSocket APIをクリーンなRustインターフェースでラップしています。

試してみる

チャプタークイズ

すべての問題に正解してレッスンを完了しましょう