use std::time::{Duration, Instant}; use actix::prelude::*; use actix_web_actors::ws; /// How often heartbeat pings are sent const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5); /// How long before lack of client response causes a timeout const CLIENT_TIMEOUT: Duration = Duration::from_secs(10); /// websocket connection is long running connection, it easier /// to handle with an actor pub struct MyWebSocket { /// Client must send ping at least once per 10 seconds (CLIENT_TIMEOUT), /// otherwise we drop connection. hb: Instant, } impl MyWebSocket { pub fn new() -> Self { Self { hb: Instant::now() } } /// helper method that sends ping to client every 5 seconds (HEARTBEAT_INTERVAL). /// /// also this method checks heartbeats from client fn hb(&self, ctx: &mut ::Context) { ctx.run_interval(HEARTBEAT_INTERVAL, |act, ctx| { // check client heartbeats if Instant::now().duration_since(act.hb) > CLIENT_TIMEOUT { // heartbeat timed out println!("Websocket Client heartbeat failed, disconnecting!"); // stop actor ctx.stop(); // don't try to send a ping return; } ctx.ping(b""); }); } } impl Actor for MyWebSocket { type Context = ws::WebsocketContext; /// Method is called on actor start. We start the heartbeat process here. fn started(&mut self, ctx: &mut Self::Context) { self.hb(ctx); } } /// Handler for `ws::Message` impl StreamHandler> for MyWebSocket { fn handle(&mut self, msg: Result, ctx: &mut Self::Context) { // process websocket messages println!("WS: {msg:?}"); match msg { Ok(ws::Message::Ping(msg)) => { self.hb = Instant::now(); ctx.pong(&msg); } Ok(ws::Message::Pong(_)) => { self.hb = Instant::now(); } Ok(ws::Message::Text(text)) => ctx.text(text), Ok(ws::Message::Binary(bin)) => ctx.binary(bin), Ok(ws::Message::Close(reason)) => { ctx.close(reason); ctx.stop(); } _ => ctx.stop(), } } }