use crate::actor::{Actor, ActorContext};
use crate::message::Message;
use futures_util::{
SinkExt, StreamExt,
stream::{SplitSink, SplitStream},
};
use bytes::Bytes;
use std::sync::Arc;
use async_trait::async_trait;
use log::{debug, info};
use web_time::Duration;
use tokio_websockets::{Message as WsMessage, WebSocketStream};
pub struct WsConn<S> {
ws_sink: Option<SplitSink<WebSocketStream<S>, WsMessage>>,
ws_stream: Option<SplitStream<WebSocketStream<S>>>,
allow_public_space: bool,
send_buf: Vec<u8>,
}
impl<S> WsConn<S>
where
S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + 'static,
{
pub fn new(ws: WebSocketStream<S>, allow_public_space: bool) -> Self {
let (sink, stream) = futures_util::StreamExt::split(ws);
Self {
ws_sink: Some(sink),
ws_stream: Some(stream),
allow_public_space,
send_buf: Vec::with_capacity(512),
}
}
async fn send_msg(&mut self, msg: &Arc<Message>, ctx: &ActorContext) {
let bytes = match msg.as_ref() {
Message::Put(put) => put.get_or_serialize(),
_ => {
msg.to_writer(&mut self.send_buf);
Bytes::from(std::mem::take(&mut self.send_buf))
}
};
ctx.metrics.record_serialization();
if let Some(sink) = &mut self.ws_sink {
let _ = sink
.feed(WsMessage::text(
String::from_utf8(bytes.to_vec()).expect("wire format is valid UTF-8"),
))
.await;
}
ctx.metrics.record_ws_sent();
}
async fn flush_sink(&mut self) {
if let Some(sink) = &mut self.ws_sink {
let _ = sink.flush().await;
}
}
}
#[async_trait]
impl<S> Actor for WsConn<S>
where
S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + 'static,
{
async fn handle(&mut self, msg: Arc<Message>, ctx: &ActorContext) {
self.send_msg(&msg, ctx).await;
self.flush_sink().await;
}
async fn handle_batch(&mut self, batch: &mut Vec<Arc<Message>>, ctx: &ActorContext) {
if self.ws_sink.is_none() {
batch.clear();
return;
}
match batch.len() {
0 => {}
1 => {
let msg = batch.drain(..).next().unwrap();
self.send_msg(&msg, ctx).await;
self.flush_sink().await;
}
_ => {
self.send_buf.clear();
self.send_buf.push(b'[');
let mut first = true;
let mut count = 0;
for msg in batch.drain(..) {
if !first {
self.send_buf.push(b',');
}
first = false;
let bytes = match msg.as_ref() {
Message::Put(put) => put.get_or_serialize(),
_ => {
let mut buf = Vec::with_capacity(64);
msg.to_writer(&mut buf);
Bytes::from(buf)
}
};
self.send_buf.extend_from_slice(&bytes);
ctx.metrics.record_serialization();
ctx.metrics.record_ws_sent();
count += 1;
if count % 16 == 0 {
crate::tokio_spawn::yield_now().await;
}
}
self.send_buf.push(b']');
let buf = std::mem::take(&mut self.send_buf);
if let Some(sink) = &mut self.ws_sink {
let _ = sink
.feed(WsMessage::text(
String::from_utf8(buf).expect("wire format is valid UTF-8"),
))
.await;
}
self.flush_sink().await;
}
}
}
async fn pre_start(&mut self, ctx: &ActorContext) {
let hi = Message::Hi {
from: ctx.addr.clone(),
peer_id: ctx.peer_id.read().clone(),
};
hi.to_writer(&mut self.send_buf);
ctx.metrics.record_serialization();
if let Some(sink) = &mut self.ws_sink {
let buf = std::mem::take(&mut self.send_buf);
let _ = sink
.feed(WsMessage::text(
String::from_utf8(buf).expect("wire format is valid UTF-8"),
))
.await;
}
ctx.metrics.record_ws_sent();
self.flush_sink().await;
let reader = self.ws_stream.take().expect("ws_stream already taken");
let ctx2 = ctx.clone();
let allow_public_space = self.allow_public_space;
ctx.child_task(async move {
let mut reader = reader;
while let Some(result) = reader.next().await {
let ws_msg = match result {
Ok(m) => m,
Err(_e) => {
break;
}
};
if ws_msg.is_text() {
let text = ws_msg.as_text().unwrap_or("");
if text.is_empty() {
continue;
}
ctx2.metrics.record_ws_received();
match Message::try_from(text, ctx2.addr.clone(), allow_public_space) {
Ok(msgs) => {
ctx2.metrics.record_parsed();
for msg in msgs {
let _ = ctx2.router.read().send(msg);
}
}
Err(e) => {
debug!("[WS] parse error: {} (len={})", e, text.len());
}
}
} else if ws_msg.is_binary() {
debug!("[WS] binary frame (ignored)");
} else if ws_msg.is_close() {
debug!("[WS] close frame received from peer");
break;
} else if ws_msg.is_ping() {
debug!("[WS] ping frame (ignored)");
} else if ws_msg.is_pong() {
debug!("[WS] pong frame (ignored)");
}
}
debug!("[WS] receive loop ended — stopping actor");
ctx2.stop();
});
}
async fn stopping(&mut self, _context: &ActorContext) {
info!("WsConn stopping — sending WebSocket Close frame");
if let Some(sink) = &mut self.ws_sink {
let close_result =
crate::tokio_time::timeout(Duration::from_secs(2), sink.close()).await;
match close_result {
Ok(Ok(())) => debug!("WsConn Close frame acknowledged"),
Ok(Err(e)) => debug!("WsConn Close error (non-fatal): {}", e),
Err(_) => debug!("WsConn Close timed out — connection dropped"),
}
}
}
}