1use std::{
2 fmt::{
3 Debug as FmtDebug,
4 Formatter,
5 Result as FmtResult,
6 },
7 sync::{
8 Arc,
9 LazyLock,
10 atomic::{
11 AtomicU64,
12 Ordering,
13 },
14 },
15};
16
17use anyhow::{
18 Result,
19 bail,
20};
21use arc_swap::ArcSwap;
22use http::{
23 HeaderMap,
24 Uri,
25};
26use kikiutils::{
27 atomic::enum_cell::AtomicEnumCell,
28 types::fx_collections::FxDashSet,
29};
30use num_enum::{
31 IntoPrimitive,
32 TryFromPrimitive,
33};
34use serde::{
35 Serialize,
36 de::DeserializeOwned,
37};
38use tokio::{
39 spawn,
40 sync::{
41 Mutex,
42 mpsc::{
43 Receiver,
44 Sender,
45 channel,
46 },
47 },
48 task::JoinHandle,
49 time::{
50 sleep,
51 timeout,
52 },
53};
54use tokio_tungstenite::tungstenite::Message;
55use tokio_util::sync::CancellationToken;
56
57#[cfg(feature = "connection-extensions")]
58mod extensions;
59
60#[cfg(feature = "connection-extensions")]
61use self::extensions::ConnectionExtensions;
62use crate::{
63 WsIoServer,
64 core::{
65 channel_capacity_from_websocket_config,
66 event::registry::WsIoEventRegistry,
67 packet::{
68 WsIoPacket,
69 WsIoPacketType,
70 },
71 traits::task::spawner::TaskSpawner,
72 types::BoxAsyncUnaryResultHandler,
73 utils::task::abort_locked_task,
74 },
75 namespace::{
76 WsIoServerNamespace,
77 operators::broadcast::WsIoServerNamespaceBroadcastOperator,
78 },
79};
80
81#[repr(u8)]
83#[derive(Debug, Eq, IntoPrimitive, PartialEq, TryFromPrimitive)]
84enum ConnectionState {
85 Activating,
86 AwaitingInit,
87 Closed,
88 Closing,
89 Created,
90 Initiating,
91 Ready,
92}
93
94pub struct WsIoServerConnection {
96 cancel_token: ArcSwap<CancellationToken>,
97 event_registry: WsIoEventRegistry<WsIoServerConnection, WsIoServerConnection>,
98 #[cfg(feature = "connection-extensions")]
99 extensions: ConnectionExtensions,
100 headers: HeaderMap,
101 id: u64,
102 init_timeout_task: Mutex<Option<JoinHandle<()>>>,
103 joined_rooms: FxDashSet<String>,
104 message_tx: Sender<Arc<Message>>,
105 namespace: Arc<WsIoServerNamespace>,
106 on_close_handler: Mutex<Option<BoxAsyncUnaryResultHandler<Self>>>,
107 request_uri: Uri,
108 state: AtomicEnumCell<ConnectionState>,
109}
110
111impl FmtDebug for WsIoServerConnection {
112 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
113 let init_timeout_task = match self.init_timeout_task.try_lock() {
114 Ok(task) => {
115 if task.is_some() {
116 "<task>"
117 } else {
118 "<none>"
119 }
120 },
121 Err(_) => "<locked>",
122 };
123
124 let on_close_handler = match self.on_close_handler.try_lock() {
125 Ok(handler) => {
126 if handler.is_some() {
127 "<handler>"
128 } else {
129 "<none>"
130 }
131 },
132 Err(_) => "<locked>",
133 };
134
135 let mut debug = f.debug_struct("WsIoServerConnection");
136 debug
137 .field("id", &self.id)
138 .field("state", &self.state)
139 .field("request_uri", &self.request_uri)
140 .field("headers", &self.headers)
141 .field("joined_rooms_len", &self.joined_rooms.len())
142 .field("message_tx", &self.message_tx)
143 .field("cancel_token", &"<cancel_token>")
144 .field("namespace", &"<namespace>")
145 .field("event_registry", &self.event_registry)
146 .field("init_timeout_task", &init_timeout_task)
147 .field("on_close_handler", &on_close_handler);
148
149 #[cfg(feature = "connection-extensions")]
150 debug.field("extensions", &self.extensions);
151
152 debug.finish()
153 }
154}
155
156impl TaskSpawner for WsIoServerConnection {
157 #[inline]
158 fn cancel_token(&self) -> Arc<CancellationToken> {
159 self.cancel_token.load_full()
160 }
161}
162
163impl WsIoServerConnection {
164 #[inline]
165 pub(crate) fn new(
166 headers: HeaderMap,
167 namespace: Arc<WsIoServerNamespace>,
168 request_uri: Uri,
169 ) -> (Arc<Self>, Receiver<Arc<Message>>) {
170 let channel_capacity = channel_capacity_from_websocket_config(&namespace.config.websocket_config);
171 let (message_tx, message_rx) = channel(channel_capacity);
172 (
173 Arc::new(Self {
174 cancel_token: ArcSwap::new(Arc::new(CancellationToken::new())),
175 event_registry: WsIoEventRegistry::new(),
176 #[cfg(feature = "connection-extensions")]
177 extensions: ConnectionExtensions::new(),
178 headers,
179 id: NEXT_CONNECTION_ID.fetch_add(1, Ordering::Relaxed),
180 init_timeout_task: Mutex::new(None),
181 joined_rooms: FxDashSet::default(),
182 message_tx,
183 namespace,
184 on_close_handler: Mutex::new(None),
185 request_uri,
186 state: AtomicEnumCell::new(ConnectionState::Created),
187 }),
188 message_rx,
189 )
190 }
191
192 #[inline]
194 fn handle_event_packet(self: &Arc<Self>, event: &str, packet_data: Option<Vec<u8>>) -> Result<()> {
195 self.event_registry.dispatch_event_packet(
196 self.clone(),
197 event,
198 &self.namespace.config.packet_codec,
199 packet_data,
200 self,
201 );
202
203 Ok(())
204 }
205
206 async fn handle_init_packet(self: &Arc<Self>, packet_data: Option<&[u8]>) -> Result<()> {
207 let state = self.state.get();
209 match state {
210 ConnectionState::AwaitingInit => self.state.try_transition(state, ConnectionState::Initiating)?,
211 _ => bail!("Received init packet in invalid state: {state:?}"),
212 }
213
214 abort_locked_task(&self.init_timeout_task).await;
216
217 if let Some(init_response_handler) = &self.namespace.config.init_response_handler {
219 timeout(
220 self.namespace.config.init_response_handler_timeout,
221 init_response_handler(self.clone(), packet_data, &self.namespace.config.packet_codec),
222 )
223 .await??
224 }
225
226 self.state
228 .try_transition(ConnectionState::Initiating, ConnectionState::Activating)?;
229
230 if let Some(middleware) = &self.namespace.config.middleware {
232 timeout(
233 self.namespace.config.middleware_execution_timeout,
234 middleware(self.clone()),
235 )
236 .await??;
237
238 self.state.ensure(ConnectionState::Activating, |state| {
240 format!("Cannot activate connection in invalid state: {state:?}")
241 })?;
242 }
243
244 if let Some(on_connect_handler) = &self.namespace.config.on_connect_handler {
246 timeout(
247 self.namespace.config.on_connect_handler_timeout,
248 on_connect_handler(self.clone()),
249 )
250 .await??;
251 }
252
253 self.state
255 .try_transition(ConnectionState::Activating, ConnectionState::Ready)?;
256
257 self.namespace.insert_connection(self.clone());
259
260 self.send_packet(&WsIoPacket::new_ready()).await?;
262
263 if let Some(on_ready_handler) = self.namespace.config.on_ready_handler.clone() {
265 self.spawn_task(on_ready_handler(self.clone()));
267 }
268
269 Ok(())
270 }
271
272 async fn send_packet(&self, packet: &WsIoPacket) -> Result<()> {
273 self.send_message(self.namespace.encode_packet_to_message(packet)?)
274 .await
275 }
276
277 pub(crate) async fn cleanup(self: &Arc<Self>) {
279 self.state.store(ConnectionState::Closing);
281
282 self.namespace.remove_connection(self.id);
284
285 let joined_rooms = self.joined_rooms.iter().map(|entry| entry.clone()).collect::<Vec<_>>();
287 for room_name in &joined_rooms {
288 self.namespace.remove_connection_id_from_room(room_name, self.id);
289 }
290
291 self.joined_rooms.clear();
292
293 abort_locked_task(&self.init_timeout_task).await;
295
296 self.cancel_token.load().cancel();
298
299 if let Some(on_close_handler) = self.on_close_handler.lock().await.take() {
301 let _ = timeout(
302 self.namespace.config.on_close_handler_timeout,
303 on_close_handler(self.clone()),
304 )
305 .await;
306 }
307
308 self.state.store(ConnectionState::Closed);
310 }
311
312 #[inline]
313 pub(crate) fn close(&self) {
314 match self.state.get() {
316 ConnectionState::Closed | ConnectionState::Closing => return,
317 _ => self.state.store(ConnectionState::Closing),
318 }
319
320 let _ = self.message_tx.try_send(Arc::new(Message::Close(None)));
322 }
323
324 pub(crate) async fn emit_event_message(&self, message: Arc<Message>) -> Result<()> {
325 self.state.ensure(ConnectionState::Ready, |state| {
326 format!("Cannot emit in invalid state: {state:?}")
327 })?;
328
329 self.send_message(message).await
330 }
331
332 pub(crate) async fn handle_incoming_packet(self: &Arc<Self>, encoded_packet: &[u8]) -> Result<()> {
333 let packet = self.namespace.config.packet_codec.decode(encoded_packet)?;
335 match packet.r#type {
336 WsIoPacketType::Event => {
337 if self.is_ready() {
338 if let Some(event) = packet.key.as_deref() {
339 return self.handle_event_packet(event, packet.data);
340 } else {
341 bail!("Event packet missing key");
342 }
343 }
344
345 Ok(())
346 },
347 WsIoPacketType::Init => self.handle_init_packet(packet.data.as_deref()).await,
348 _ => Ok(()),
349 }
350 }
351
352 pub(crate) async fn init(self: &Arc<Self>) -> Result<()> {
353 self.state.ensure(ConnectionState::Created, |state| {
355 format!("Cannot init connection in invalid state: {state:?}")
356 })?;
357
358 let init_request_data = if let Some(init_request_handler) = &self.namespace.config.init_request_handler {
360 timeout(
361 self.namespace.config.init_request_handler_timeout,
362 init_request_handler(self.clone(), &self.namespace.config.packet_codec),
363 )
364 .await??
365 } else {
366 None
367 };
368
369 self.state
371 .try_transition(ConnectionState::Created, ConnectionState::AwaitingInit)?;
372
373 let connection = self.clone();
375 *self.init_timeout_task.lock().await = Some(spawn(async move {
376 sleep(connection.namespace.config.init_response_timeout).await;
377 if connection.state.is(ConnectionState::AwaitingInit) {
378 connection.close();
379 }
380 }));
381
382 self.send_packet(&WsIoPacket::new_init(init_request_data)).await
384 }
385
386 pub(crate) async fn send_message(&self, message: Arc<Message>) -> Result<()> {
387 Ok(self.message_tx.send(message).await?)
388 }
389
390 pub async fn disconnect(&self) {
392 let _ = self.send_packet(&WsIoPacket::new_disconnect()).await;
393 self.close()
394 }
395
396 pub async fn emit<D: Serialize>(&self, event: impl AsRef<str>, data: Option<&D>) -> Result<()> {
397 self.emit_event_message(
398 self.namespace.encode_packet_to_message(&WsIoPacket::new_event(
399 event.as_ref(),
400 data.map(|data| self.namespace.config.packet_codec.encode_data(data))
401 .transpose()?,
402 ))?,
403 )
404 .await
405 }
406
407 #[inline]
408 pub fn except(
409 self: &Arc<Self>,
410 room_names: impl IntoIterator<Item = impl Into<String>>,
411 ) -> WsIoServerNamespaceBroadcastOperator {
412 self.namespace.except(room_names).except_connection_ids([self.id])
413 }
414
415 #[cfg(feature = "connection-extensions")]
416 #[inline]
417 pub fn extensions(&self) -> &ConnectionExtensions {
418 &self.extensions
419 }
420
421 #[inline]
422 pub fn headers(&self) -> &HeaderMap {
423 &self.headers
424 }
425
426 #[inline]
427 pub fn id(&self) -> u64 {
428 self.id
429 }
430
431 #[inline]
432 pub fn is_ready(&self) -> bool {
433 self.state.is(ConnectionState::Ready)
434 }
435
436 #[inline]
437 pub fn join(self: &Arc<Self>, room_names: impl IntoIterator<Item = impl Into<String>>) {
438 for room_name in room_names {
439 let room_name = room_name.into();
440 self.namespace.add_connection_id_to_room(&room_name, self.id);
441 self.joined_rooms.insert(room_name);
442 }
443 }
444
445 #[inline]
446 pub fn leave(self: &Arc<Self>, room_names: impl IntoIterator<Item = impl Into<String>>) {
447 for room_name in room_names {
448 let room_name = &room_name.into();
449 self.namespace.remove_connection_id_from_room(room_name, self.id);
450
451 self.joined_rooms.remove(room_name);
452 }
453 }
454
455 #[inline]
456 pub fn namespace(&self) -> Arc<WsIoServerNamespace> {
457 self.namespace.clone()
458 }
459
460 #[inline]
461 pub fn off(&self, event: impl AsRef<str>) {
462 self.event_registry.off(event.as_ref());
463 }
464
465 #[inline]
466 pub fn off_by_handler_id(&self, event: impl AsRef<str>, handler_id: u32) {
467 self.event_registry.off_by_handler_id(event.as_ref(), handler_id);
468 }
469
470 #[inline]
471 pub fn on<H, Fut, D>(&self, event: impl AsRef<str>, handler: H) -> u32
472 where
473 H: Fn(Arc<WsIoServerConnection>, Arc<D>) -> Fut + Send + Sync + 'static,
474 Fut: Future<Output = Result<()>> + Send + 'static,
475 D: DeserializeOwned + Send + Sync + 'static,
476 {
477 self.event_registry.on(event.as_ref(), handler)
478 }
479
480 pub async fn on_close<H, Fut>(&self, handler: H)
481 where
482 H: Fn(Arc<WsIoServerConnection>) -> Fut + Send + Sync + 'static,
483 Fut: Future<Output = Result<()>> + Send + 'static,
484 {
485 *self.on_close_handler.lock().await = Some(Box::new(move |connection| Box::pin(handler(connection))));
486 }
487
488 #[inline]
489 pub fn request_uri(&self) -> &Uri {
490 &self.request_uri
491 }
492
493 #[inline]
494 pub fn server(&self) -> WsIoServer {
495 self.namespace.server()
496 }
497
498 #[inline]
499 pub fn to(
500 self: &Arc<Self>,
501 room_names: impl IntoIterator<Item = impl Into<String>>,
502 ) -> WsIoServerNamespaceBroadcastOperator {
503 self.namespace.to(room_names).except_connection_ids([self.id])
504 }
505}
506
507static NEXT_CONNECTION_ID: LazyLock<AtomicU64> = LazyLock::new(|| AtomicU64::new(0));
509
510#[cfg(test)]
511mod tests {
512 use http::{
513 HeaderMap,
514 Uri,
515 };
516
517 use super::*;
518
519 fn create_test_connection() -> Arc<WsIoServerConnection> {
520 let server = Arc::new(WsIoServer::builder().build());
521 let namespace = server.new_namespace_builder("/socket").register().unwrap();
522 let (connection, _rx) =
523 WsIoServerConnection::new(HeaderMap::new(), namespace, Uri::from_static("http://localhost"));
524
525 connection
526 }
527
528 #[tokio::test]
529 async fn test_handle_incoming_packet_decode_error() {
530 let connection = create_test_connection();
531 let garbage_data = b"obviously not valid json or messagepack";
532 let result = connection.handle_incoming_packet(garbage_data).await;
534 assert!(result.is_err(), "Decoding garbage payload should trigger an error");
535 }
536
537 #[tokio::test]
538 async fn test_handle_init_packet_in_invalid_state() {
539 let connection = create_test_connection();
540 assert_eq!(connection.state.get(), ConnectionState::Created);
541
542 let encoded = b"[2,null,null]";
545
546 let result = connection.handle_incoming_packet(encoded).await;
548 assert!(
549 result.is_err(),
550 "Should error because state is Created, not AwaitingInit"
551 );
552
553 assert!(result.unwrap_err().to_string().contains("invalid state"));
554 }
555
556 #[tokio::test]
557 async fn test_handle_event_packet_missing_key() {
558 let connection = create_test_connection();
559
560 connection.state.store(ConnectionState::Ready);
562
563 let encoded = b"[1,null,null]";
565
566 let result = connection.handle_incoming_packet(encoded).await;
567 assert!(result.is_err(), "Should bail on missing event key");
568 assert_eq!(result.unwrap_err().to_string(), "Event packet missing key");
569 }
570
571 #[tokio::test]
572 async fn test_connection_close_state_transitions() {
573 let connection = create_test_connection();
574 assert_eq!(connection.state.get(), ConnectionState::Created);
575
576 connection.close();
577 assert_eq!(connection.state.get(), ConnectionState::Closing);
578
579 connection.close();
581 assert_eq!(connection.state.get(), ConnectionState::Closing);
582 }
583
584 #[tokio::test]
585 async fn test_connection_cleanup() {
586 let connection = create_test_connection();
587 let namespace = connection.namespace();
588
589 namespace.insert_connection(connection.clone());
591 assert_eq!(namespace.connection_count(), 1);
592
593 connection.join(["room_a", "room_b"]);
594 assert!(connection.joined_rooms.contains("room_a"));
595
596 connection.cleanup().await;
597
598 assert_eq!(connection.state.get(), ConnectionState::Closed);
599 assert!(connection.joined_rooms.is_empty());
600 assert_eq!(namespace.connection_count(), 0);
601 }
602}