zero4rs 2.0.0

zero4rs is a powerful, pragmatic, and extremely fast web framework for Rust
Documentation
use actix::fut;

use actix::ActorContext;
use actix::ActorFutureExt;

use actix::{Actor, Addr, ContextFutureSpawner, Running, StreamHandler, WrapFuture};
use actix::{AsyncContext, Handler};
use actix_web_actors::ws;
use actix_web_actors::ws::Message::Text;

use std::time::{Duration, Instant};
use uuid::Uuid;

use crate::websocket::lobbies::lobby::Lobby;
use crate::websocket::lobbies::messages::{ClientActorMessage, Connect, Disconnect, WsMessage};

const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5);
const CLIENT_TIMEOUT: Duration = Duration::from_secs(10);

pub struct WsConn {
    // each Socket exists in a ‘room’,
    // which in this implementation will just be a simple HashMap that maps Uuid to List of Socket Ids.
    room: Uuid,
    // 这是套接字所在大厅的地址。这将用于向大厅发送数据。
    // 向大厅发送信息时可能如下所示:self.addr.do_send('hi!')。
    lobby_addr: Addr<Lobby>,
    // 收到最后一次心跳以来的时间。
    hb: Instant,
    // 分配给该套接字的 ID
    // we can /whisper <id> hello! to whisper to that client.
    id: Uuid,
}

impl WsConn {
    pub fn new(room: Uuid, lobby: Addr<Lobby>) -> WsConn {
        WsConn {
            id: Uuid::now_v7(),
            room,
            hb: Instant::now(),
            lobby_addr: lobby,
        }
    }
}

impl WsConn {
    fn hb(&self, ctx: &mut ws::WebsocketContext<Self>) {
        ctx.run_interval(HEARTBEAT_INTERVAL, |act, ctx| {
            if Instant::now().duration_since(act.hb) > CLIENT_TIMEOUT {
                log::warn!("Disconnecting failed heartbeat");
                ctx.stop();
                return;
            }

            ctx.ping(b"hi");
        });
    }
}

impl Handler<WsMessage> for WsConn {
    type Result = ();

    // if the server puts a WsMessage (Which we need to define) mail into our mailbox,
    // all we do is send it straight to the client
    fn handle(&mut self, msg: WsMessage, ctx: &mut Self::Context) {
        ctx.text(msg.0);
    }
}

// WsConn 只是一个普通的旧 Rust 结构。要将其转换为 actor,我们需要在其上实现 Actor 特征。
impl Actor for WsConn {
    type Context = ws::WebsocketContext<Self>;

    fn started(&mut self, ctx: &mut Self::Context) {
        self.hb(ctx);

        let addr = ctx.address();

        self.lobby_addr
            // send (asynchronously), or do_send (同步)
            .send(
                // 新的连接
                Connect {
                    addr: addr.recipient(),
                    lobby_id: self.room,
                    self_id: self.id,
                },
            )
            .into_actor(self)
            .then(|res, _, ctx| {
                match res {
                    Ok(_res) => (),
                    _ => ctx.stop(),
                }
                fut::ready(())
            })
            .wait(ctx);
    }

    fn stopping(&mut self, _: &mut Self::Context) -> Running {
        self.lobby_addr.do_send(
            // 停止时, 连接断开
            Disconnect {
                id: self.id,
                room_id: self.room,
            },
        );

        Running::Stop
    }
}

impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for WsConn {
    fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
        match msg {
            // 客户端向发送心跳(ping), 回复一个 pong.
            Ok(ws::Message::Ping(msg)) => {
                self.hb = Instant::now();
                ctx.pong(&msg);
            }
            // 客户端向发送心跳(ping), 此处更新存活时间
            Ok(ws::Message::Pong(_)) => {
                self.hb = Instant::now();
            }
            // 客户段发送的二进制消息,我们将把它发送到 WebSocket 上下文,WebSocket 上下文会弄清楚如何处理它。
            // 实际上,这应该永远不会被触发。
            Ok(ws::Message::Binary(bin)) => ctx.binary(bin),
            // 客户端主动断开连接。
            Ok(ws::Message::Close(reason)) => {
                ctx.close(reason);
                ctx.stop();
            }
            // 我们不会响应连续帧(简而言之,这些是无法适应一个消息的 WebSocket 消息)。
            Ok(ws::Message::Continuation(_)) => {
                ctx.stop();
            }
            // 在 nop 时执行 nop(无操作)。
            Ok(ws::Message::Nop) => (),
            // 在文本消息上,(这个我们会做最多的!)将其发送到大厅。大厅将负责将其代理到需要去的地方。
            Ok(Text(s)) => self.lobby_addr.do_send(
                // 客户段发过来的信息
                ClientActorMessage {
                    id: self.id,
                    msg: s.to_string(),
                    room_id: self.room,
                },
            ),
            // 在出现错误时,打印日志。你可能想合理地实现在这里该做什么。
            Err(e) => {
                log::error!("error={:?}", e);
            }
        }
    }
}