1use std::collections::HashMap;
2use std::future::Future;
3use std::panic::AssertUnwindSafe;
4use std::sync::Arc;
5use std::sync::atomic::Ordering;
6use std::time::Duration;
7
8use bytes::Bytes;
9use futures_util::{FutureExt, SinkExt, StreamExt, future::BoxFuture};
10use serde::de::DeserializeOwned;
11use serde_json::{Value, json};
12use tokio::net::{TcpListener, TcpStream};
13use tokio::sync::{Semaphore, mpsc};
14use tokio::time::{MissedTickBehavior, interval, timeout};
15use tokio_tungstenite::{accept_async, tungstenite::Message};
16use uuid::Uuid;
17use validator::Validate;
18use wscall_protocol::{
19 EncryptionKind, ErrorPayload, FileAttachment, FrameCodec, PacketBody, PacketEnvelope,
20};
21
22use crate::server_types::{
23 ApiContext, ApiError, EventContext, ExceptionContext, ServerConnectionContext,
24 ServerDisconnectContext, ServerError, ServerHandle, ServerOutbound, ServerState,
25};
26
27const SERVER_IDLE_TIMEOUT: Duration = Duration::from_secs(45);
28const SERVER_HEARTBEAT_INTERVAL: Duration = Duration::from_secs(15);
29const SERVER_OUTBOUND_QUEUE_CAPACITY: usize = 256;
30const SERVER_DEFAULT_MAX_IN_FLIGHT: usize = 64;
32const SERVER_CLOSE_GRACE: Duration = Duration::from_millis(500);
34
35type ApiHandler =
36 Arc<dyn Fn(ApiContext) -> BoxFuture<'static, Result<Value, ApiError>> + Send + Sync>;
37type Filter =
38 Arc<dyn Fn(ApiContext) -> BoxFuture<'static, Result<ApiContext, ApiError>> + Send + Sync>;
39type EventHandler =
40 Arc<dyn Fn(EventContext) -> BoxFuture<'static, Result<Value, ApiError>> + Send + Sync>;
41type ConnectionHandler =
42 Arc<dyn Fn(ServerConnectionContext) -> BoxFuture<'static, ()> + Send + Sync>;
43type DisconnectHandler =
44 Arc<dyn Fn(ServerDisconnectContext) -> BoxFuture<'static, ()> + Send + Sync>;
45type ExceptionHandler =
46 Arc<dyn Fn(ExceptionContext) -> BoxFuture<'static, ErrorPayload> + Send + Sync>;
47
48struct ApiRequestInput {
49 request_id: u64,
50 route: String,
51 params: Value,
52 attachments: Vec<FileAttachment>,
53 metadata: Value,
54}
55
56struct EventEmitInput {
57 event_id: u64,
58 name: String,
59 data: Value,
60 attachments: Vec<FileAttachment>,
61 metadata: Value,
62 storage_id: Option<u64>,
63}
64
65impl ServerHandle {
66 pub async fn broadcast_event(
73 &self,
74 name: impl Into<String>,
75 data: Value,
76 attachments: Vec<FileAttachment>,
77 ) -> Result<(), ApiError> {
78 self.broadcast_event_inner(name, data, attachments, None)
79 .await
80 }
81
82 pub async fn broadcast_persisted_event(
85 &self,
86 name: impl Into<String>,
87 data: Value,
88 attachments: Vec<FileAttachment>,
89 storage_id: u64,
90 ) -> Result<(), ApiError> {
91 self.broadcast_event_inner(name, data, attachments, Some(storage_id))
92 .await
93 }
94
95 async fn broadcast_event_inner(
96 &self,
97 name: impl Into<String>,
98 data: Value,
99 attachments: Vec<FileAttachment>,
100 storage_id: Option<u64>,
101 ) -> Result<(), ApiError> {
102 let event_id = self.state.next_event_id.fetch_add(1, Ordering::Relaxed) + 1;
103 let packet = PacketEnvelope::with_encryption(
104 PacketBody::EventEmit {
105 event_id,
106 name: name.into(),
107 data,
108 attachments,
109 metadata: json!({ "source": "server" }),
110 expect_ack: true,
111 storage_id,
112 },
113 self.default_encryption,
114 );
115
116 let encoded = self
117 .codec
118 .encode(&packet)
119 .map_err(|_| ApiError::internal("failed to encode broadcast event"))?;
120 let encoded = Bytes::from(encoded);
121
122 for entry in self.state.clients.iter() {
123 if entry
124 .value()
125 .try_send(ServerOutbound::PreEncoded(encoded.clone()))
126 .is_err()
127 {
128 tracing::warn!("broadcast: outbound queue full, dropping event for a connection");
129 }
130 }
131 Ok(())
132 }
133
134 pub async fn send_event_to(
138 &self,
139 connection_id: &str,
140 name: impl Into<String>,
141 data: Value,
142 attachments: Vec<FileAttachment>,
143 ) -> Result<(), ApiError> {
144 self.send_event_to_inner(connection_id, name, data, attachments, None)
145 .await
146 }
147
148 pub async fn send_persisted_event_to(
151 &self,
152 connection_id: &str,
153 name: impl Into<String>,
154 data: Value,
155 attachments: Vec<FileAttachment>,
156 storage_id: u64,
157 ) -> Result<(), ApiError> {
158 self.send_event_to_inner(connection_id, name, data, attachments, Some(storage_id))
159 .await
160 }
161
162 async fn send_event_to_inner(
163 &self,
164 connection_id: &str,
165 name: impl Into<String>,
166 data: Value,
167 attachments: Vec<FileAttachment>,
168 storage_id: Option<u64>,
169 ) -> Result<(), ApiError> {
170 let event_id = self.state.next_event_id.fetch_add(1, Ordering::Relaxed) + 1;
171 let packet = PacketEnvelope::with_encryption(
172 PacketBody::EventEmit {
173 event_id,
174 name: name.into(),
175 data,
176 attachments,
177 metadata: json!({ "source": "server" }),
178 expect_ack: true,
179 storage_id,
180 },
181 self.default_encryption,
182 );
183
184 let encoded = self
185 .codec
186 .encode(&packet)
187 .map_err(|_| ApiError::internal("failed to encode direct event"))?;
188 let encoded = Bytes::from(encoded);
189
190 let entry = self
191 .state
192 .clients
193 .get(connection_id)
194 .ok_or_else(|| ApiError::not_found("target connection not found"))?;
195 entry
196 .try_send(ServerOutbound::PreEncoded(encoded))
197 .map_err(|_| ApiError::internal("failed to queue direct event"))
198 }
199
200 pub async fn connection_count(&self) -> usize {
202 self.state.clients.len()
203 }
204}
205
206pub struct WscallServer {
207 state: Arc<ServerState>,
208 routes: HashMap<String, ApiHandler>,
209 filters: Vec<Filter>,
210 event_handlers: HashMap<String, EventHandler>,
211 connection_handlers: Vec<ConnectionHandler>,
212 disconnect_handlers: Vec<DisconnectHandler>,
213 exception_handler: Option<ExceptionHandler>,
214 codec: FrameCodec,
215 default_encryption: EncryptionKind,
216 max_connections: Option<usize>,
218 max_in_flight: usize,
220}
221
222impl Default for WscallServer {
223 fn default() -> Self {
224 Self::new()
225 }
226}
227
228impl WscallServer {
229 pub fn new() -> Self {
230 Self {
231 state: Arc::new(ServerState::new()),
232 routes: HashMap::new(),
233 filters: Vec::new(),
234 event_handlers: HashMap::new(),
235 connection_handlers: Vec::new(),
236 disconnect_handlers: Vec::new(),
237 exception_handler: None,
238 codec: FrameCodec::plaintext(),
239 default_encryption: EncryptionKind::None,
240 max_connections: None,
241 max_in_flight: SERVER_DEFAULT_MAX_IN_FLIGHT,
242 }
243 }
244
245 pub fn with_chacha20_key(mut self, key: [u8; 32]) -> Self {
246 self.codec = self.codec.clone().with_chacha20_key(key);
247 self.default_encryption = EncryptionKind::ChaCha20;
248 self
249 }
250
251 pub fn with_aes256_key(mut self, key: [u8; 32]) -> Self {
252 self.codec = self.codec.clone().with_aes256_key(key);
253 self.default_encryption = EncryptionKind::Aes256;
254 self
255 }
256
257 pub fn with_max_connections(mut self, max: usize) -> Self {
263 self.max_connections = Some(max);
264 self
265 }
266
267 pub fn with_max_in_flight(mut self, max: usize) -> Self {
270 self.max_in_flight = max;
271 self
272 }
273
274 pub fn handle(&self) -> ServerHandle {
275 ServerHandle {
276 state: Arc::clone(&self.state),
277 codec: self.codec.clone(),
278 default_encryption: self.default_encryption,
279 }
280 }
281
282 pub fn route<F, Fut>(&mut self, route: impl Into<String>, handler: F)
283 where
284 F: Fn(ApiContext) -> Fut + Send + Sync + 'static,
285 Fut: Future<Output = Result<Value, ApiError>> + Send + 'static,
286 {
287 let handler = Arc::new(move |ctx: ApiContext| {
288 Box::pin(handler(ctx)) as BoxFuture<'static, Result<Value, ApiError>>
289 });
290 self.routes.insert(route.into(), handler);
291 }
292
293 pub fn typed_route<T, F, Fut>(&mut self, route: impl Into<String>, handler: F)
294 where
295 T: DeserializeOwned + Send + 'static,
296 F: Fn(ApiContext, T) -> Fut + Send + Sync + 'static,
297 Fut: Future<Output = Result<Value, ApiError>> + Send + 'static,
298 {
299 let handler = Arc::new(handler);
300 self.route(route, move |ctx| {
301 let handler = Arc::clone(&handler);
302 let params = ctx.bind::<T>();
303 async move {
304 let params = params?;
305 handler(ctx, params).await
306 }
307 });
308 }
309
310 pub fn validated_route<T, F, Fut>(&mut self, route: impl Into<String>, handler: F)
311 where
312 T: DeserializeOwned + Validate + Send + 'static,
313 F: Fn(ApiContext, T) -> Fut + Send + Sync + 'static,
314 Fut: Future<Output = Result<Value, ApiError>> + Send + 'static,
315 {
316 let handler = Arc::new(handler);
317 self.route(route, move |ctx| {
318 let handler = Arc::clone(&handler);
319 let params = ctx.bind_validated::<T>();
320 async move {
321 let params = params?;
322 handler(ctx, params).await
323 }
324 });
325 }
326
327 pub fn filter<F, Fut>(&mut self, filter: F)
328 where
329 F: Fn(ApiContext) -> Fut + Send + Sync + 'static,
330 Fut: Future<Output = Result<ApiContext, ApiError>> + Send + 'static,
331 {
332 let filter = Arc::new(move |ctx: ApiContext| {
333 Box::pin(filter(ctx)) as BoxFuture<'static, Result<ApiContext, ApiError>>
334 });
335 self.filters.push(filter);
336 }
337
338 pub fn event_handler<F, Fut>(&mut self, name: impl Into<String>, handler: F)
339 where
340 F: Fn(EventContext) -> Fut + Send + Sync + 'static,
341 Fut: Future<Output = Result<Value, ApiError>> + Send + 'static,
342 {
343 let handler = Arc::new(move |ctx: EventContext| {
344 Box::pin(handler(ctx)) as BoxFuture<'static, Result<Value, ApiError>>
345 });
346 self.event_handlers.insert(name.into(), handler);
347 }
348
349 pub fn on_connected<F, Fut>(&mut self, handler: F)
350 where
351 F: Fn(ServerConnectionContext) -> Fut + Send + Sync + 'static,
352 Fut: Future<Output = ()> + Send + 'static,
353 {
354 let handler = Arc::new(move |ctx: ServerConnectionContext| {
355 Box::pin(handler(ctx)) as BoxFuture<'static, ()>
356 });
357 self.connection_handlers.push(handler);
358 }
359
360 pub fn on_disconnected<F, Fut>(&mut self, handler: F)
361 where
362 F: Fn(ServerDisconnectContext) -> Fut + Send + Sync + 'static,
363 Fut: Future<Output = ()> + Send + 'static,
364 {
365 let handler = Arc::new(move |ctx: ServerDisconnectContext| {
366 Box::pin(handler(ctx)) as BoxFuture<'static, ()>
367 });
368 self.disconnect_handlers.push(handler);
369 }
370
371 pub fn exception_handler<F, Fut>(&mut self, handler: F)
372 where
373 F: Fn(ExceptionContext) -> Fut + Send + Sync + 'static,
374 Fut: Future<Output = ErrorPayload> + Send + 'static,
375 {
376 self.exception_handler = Some(Arc::new(move |ctx: ExceptionContext| {
377 Box::pin(handler(ctx)) as BoxFuture<'static, ErrorPayload>
378 }));
379 }
380
381 pub async fn listen(self, address: &str) -> Result<(), ServerError> {
382 let listener = TcpListener::bind(address).await?;
383 tracing::info!(%address, "wscall server listening on ws://{address}/socket");
384
385 let shared = Arc::new(self);
386 let conn_sem = shared
387 .max_connections
388 .map(|max| Arc::new(Semaphore::new(max)));
389
390 loop {
391 let permit = match &conn_sem {
395 Some(sem) => match sem.clone().acquire_owned().await {
396 Ok(permit) => Some(permit),
397 Err(_) => continue,
398 },
399 None => None,
400 };
401
402 let (stream, peer) = listener.accept().await?;
403 let _ = TcpStream::set_nodelay(&stream, true);
405
406 let server = Arc::clone(&shared);
407 tokio::spawn(async move {
408 let _permit = permit;
409 if let Err(error) = server.serve_connection(stream, peer).await {
410 tracing::warn!(?error, ?peer, "connection failed");
411 }
412 });
413 }
414 }
415
416 async fn serve_connection(
417 self: Arc<Self>,
418 stream: TcpStream,
419 peer: std::net::SocketAddr,
420 ) -> Result<(), ServerError> {
421 let websocket = accept_async(stream).await?;
422 let connection_id = Uuid::now_v7().to_string();
423 let (mut sink, mut stream) = websocket.split();
424 let (tx, mut rx) = mpsc::channel::<ServerOutbound>(SERVER_OUTBOUND_QUEUE_CAPACITY);
425 let peer_addr = Some(peer);
426
427 self.state.clients.insert(connection_id.clone(), tx.clone());
428
429 self.notify_connected(&connection_id, peer_addr).await;
430
431 let mut writer = tokio::spawn(async move {
432 let mut ticker = interval(SERVER_HEARTBEAT_INTERVAL);
433 ticker.set_missed_tick_behavior(MissedTickBehavior::Delay);
434 loop {
435 tokio::select! {
436 maybe_outbound = rx.recv() => {
437 let Some(outbound) = maybe_outbound else {
438 break Ok::<(), ServerError>(());
439 };
440 match outbound {
441 ServerOutbound::PreEncoded(bytes) => {
442 sink.send(Message::Binary(bytes.to_vec())).await?;
443 }
444 ServerOutbound::Pong(payload) => {
445 sink.send(Message::Pong(payload)).await?;
446 }
447 ServerOutbound::Close => {
448 let _ = sink.send(Message::Close(None)).await;
449 break Ok(());
450 }
451 }
452 }
453 _ = ticker.tick() => {
454 if let Err(error) = sink.send(Message::Ping(Vec::new())).await {
455 break Err(ServerError::WebSocket(error));
456 }
457 }
458 }
459 }
460 });
461
462 let in_flight = Arc::new(Semaphore::new(self.max_in_flight));
466
467 let result = async {
468 self.handle()
469 .send_event_to(
470 &connection_id,
471 "system.notice",
472 json!({ "message": "connected", "connection_id": connection_id }),
473 Vec::new(),
474 )
475 .await
476 .map_err(ServerError::Api)?;
477
478 loop {
479 let next_message = timeout(SERVER_IDLE_TIMEOUT, stream.next()).await;
480 let Some(message) =
481 next_message.map_err(|_| ServerError::IdleTimeout(connection_id.clone()))?
482 else {
483 break Ok(());
484 };
485
486 match message? {
487 Message::Binary(bytes) => {
488 let packet = self.codec.decode(&bytes)?;
489
490 let spawn_handler = matches!(
491 &packet.body,
492 PacketBody::ApiRequest { .. } | PacketBody::EventEmit { .. }
493 );
494
495 if spawn_handler {
496 let permit = in_flight.clone().acquire_owned().await.map_err(|_| {
497 ServerError::Api(ApiError::internal("in-flight semaphore closed"))
498 })?;
499 let server = Arc::clone(&self);
500 let conn_id = connection_id.clone();
501 tokio::spawn(async move {
502 let _permit = permit;
503 if let Err(error) =
504 server.process_packet(&conn_id, peer_addr, packet).await
505 {
506 tracing::warn!(%error, "packet processing failed");
507 }
508 });
509 } else {
510 self.process_packet(&connection_id, peer_addr, packet)
513 .await?;
514 }
515 }
516 Message::Close(_) => break Ok(()),
517 Message::Ping(payload) => {
518 if tx
519 .send(ServerOutbound::Pong(payload.to_vec()))
520 .await
521 .is_err()
522 {
523 break Ok(());
524 }
525 }
526 Message::Pong(_) => {}
527 Message::Text(_) => {}
528 Message::Frame(_) => {}
529 }
530 }
531 }
532 .await;
533
534 self.state.clients.remove(&connection_id);
538 let _ = tx.try_send(ServerOutbound::Close);
539 let _ = timeout(SERVER_CLOSE_GRACE, &mut writer).await;
540 writer.abort();
541 self.notify_disconnected(&connection_id, peer_addr, Self::disconnect_reason(&result))
542 .await;
543 result
544 }
545
546 async fn process_packet(
547 &self,
548 connection_id: &str,
549 peer_addr: Option<std::net::SocketAddr>,
550 packet: PacketEnvelope,
551 ) -> Result<(), ServerError> {
552 match packet.body {
553 PacketBody::ApiRequest {
554 request_id,
555 route,
556 params,
557 attachments,
558 metadata,
559 } => {
560 let response = self
561 .run_api_request(
562 connection_id,
563 peer_addr,
564 ApiRequestInput {
565 request_id,
566 route,
567 params,
568 attachments,
569 metadata,
570 },
571 )
572 .await;
573 self.queue_for(connection_id, response).await?;
574 }
575 PacketBody::EventEmit {
576 event_id,
577 name,
578 data,
579 attachments,
580 metadata,
581 storage_id,
582 ..
583 } => {
584 let ack = self
585 .run_event(
586 connection_id,
587 peer_addr,
588 EventEmitInput {
589 event_id,
590 name,
591 data,
592 attachments,
593 metadata,
594 storage_id,
595 },
596 )
597 .await;
598 self.queue_for(connection_id, ack).await?;
599 }
600 PacketBody::EventAck {
601 event_id,
602 ok,
603 receipt,
604 error,
605 } => {
606 tracing::debug!(
607 connection_id,
608 event_id,
609 ok,
610 ?receipt,
611 ?error,
612 "received event ack from client",
613 );
614 }
615 PacketBody::ApiResponse { .. } => {}
616 }
617 Ok(())
618 }
619
620 async fn queue_for(
626 &self,
627 connection_id: &str,
628 packet: PacketEnvelope,
629 ) -> Result<(), ServerError> {
630 let encoded = self.codec.encode(&packet)?;
631 let encoded = Bytes::from(encoded);
632
633 let sender = match self.state.clients.get(connection_id) {
636 Some(entry) => entry.clone(),
637 None => {
638 return Err(ServerError::Api(ApiError::not_found(
639 "connection is closed",
640 )));
641 }
642 };
643
644 sender
645 .send(ServerOutbound::PreEncoded(encoded))
646 .await
647 .map_err(|_| ServerError::Api(ApiError::internal("failed to queue outbound packet")))
648 }
649
650 async fn run_api_request(
651 &self,
652 connection_id: &str,
653 peer_addr: Option<std::net::SocketAddr>,
654 request: ApiRequestInput,
655 ) -> PacketEnvelope {
656 let ApiRequestInput {
657 request_id,
658 route,
659 params,
660 attachments,
661 metadata,
662 } = request;
663
664 let mut ctx = ApiContext {
665 connection_id: connection_id.to_string(),
666 peer_addr,
667 request_id,
668 route: route.clone(),
669 params,
670 attachments,
671 metadata,
672 server: self.handle(),
673 };
674
675 for filter in &self.filters {
676 match filter(ctx).await {
677 Ok(next_ctx) => ctx = next_ctx,
678 Err(error) => {
679 return self
680 .api_error_packet(connection_id, Some(request_id), route, error)
681 .await;
682 }
683 }
684 }
685
686 let Some(handler) = self.routes.get(&ctx.route) else {
687 return self
688 .api_error_packet(
689 connection_id,
690 Some(request_id),
691 route,
692 ApiError::not_found("route not found"),
693 )
694 .await;
695 };
696
697 match AssertUnwindSafe(handler(ctx)).catch_unwind().await {
698 Ok(Ok(data)) => PacketEnvelope::with_encryption(
699 PacketBody::ApiResponse {
700 request_id,
701 ok: true,
702 status: 200,
703 data,
704 error: None,
705 metadata: json!({}),
706 },
707 self.default_encryption,
708 ),
709 Ok(Err(error)) => {
710 self.api_error_packet(connection_id, Some(request_id), route, error)
711 .await
712 }
713 Err(_) => {
714 self.api_error_packet(
715 connection_id,
716 Some(request_id),
717 route,
718 ApiError::internal("handler panicked"),
719 )
720 .await
721 }
722 }
723 }
724
725 async fn run_event(
726 &self,
727 connection_id: &str,
728 peer_addr: Option<std::net::SocketAddr>,
729 event: EventEmitInput,
730 ) -> PacketEnvelope {
731 let EventEmitInput {
732 event_id,
733 name,
734 data,
735 attachments,
736 metadata,
737 storage_id,
738 } = event;
739
740 let ctx = EventContext {
741 connection_id: connection_id.to_string(),
742 peer_addr,
743 event_id,
744 name: name.clone(),
745 data,
746 attachments,
747 metadata,
748 storage_id,
749 server: self.handle(),
750 };
751
752 let Some(handler) = self.event_handlers.get(&name) else {
753 return PacketEnvelope::with_encryption(
754 PacketBody::EventAck {
755 event_id,
756 ok: false,
757 receipt: json!({}),
758 error: Some(ApiError::not_found("event handler not found").into_payload()),
759 },
760 self.default_encryption,
761 );
762 };
763
764 match AssertUnwindSafe(handler(ctx)).catch_unwind().await {
765 Ok(Ok(receipt)) => PacketEnvelope::with_encryption(
766 PacketBody::EventAck {
767 event_id,
768 ok: true,
769 receipt,
770 error: None,
771 },
772 self.default_encryption,
773 ),
774 Ok(Err(error)) => PacketEnvelope::with_encryption(
775 PacketBody::EventAck {
776 event_id,
777 ok: false,
778 receipt: json!({}),
779 error: Some(
780 self.map_exception(ExceptionContext {
781 connection_id: connection_id.to_string(),
782 request_id: Some(event_id),
783 target: name,
784 message_kind: "event",
785 error,
786 })
787 .await,
788 ),
789 },
790 self.default_encryption,
791 ),
792 Err(_) => PacketEnvelope::with_encryption(
793 PacketBody::EventAck {
794 event_id,
795 ok: false,
796 receipt: json!({}),
797 error: Some(
798 self.map_exception(ExceptionContext {
799 connection_id: connection_id.to_string(),
800 request_id: Some(event_id),
801 target: name,
802 message_kind: "event",
803 error: ApiError::internal("event handler panicked"),
804 })
805 .await,
806 ),
807 },
808 self.default_encryption,
809 ),
810 }
811 }
812
813 async fn api_error_packet(
814 &self,
815 connection_id: &str,
816 request_id: Option<u64>,
817 route: String,
818 error: ApiError,
819 ) -> PacketEnvelope {
820 let request_id = request_id.unwrap_or(0);
821 let status = error.status;
822 let payload = self
823 .map_exception(ExceptionContext {
824 connection_id: connection_id.to_string(),
825 request_id: Some(request_id),
826 target: route,
827 message_kind: "api",
828 error,
829 })
830 .await;
831
832 PacketEnvelope::with_encryption(
833 PacketBody::ApiResponse {
834 request_id,
835 ok: false,
836 status,
837 data: json!({}),
838 error: Some(payload),
839 metadata: json!({}),
840 },
841 self.default_encryption,
842 )
843 }
844
845 async fn notify_connected(&self, connection_id: &str, peer_addr: Option<std::net::SocketAddr>) {
846 let handlers = self.connection_handlers.clone();
847 for handler in handlers {
848 let context = ServerConnectionContext {
849 connection_id: connection_id.to_string(),
850 peer_addr,
851 server: self.handle(),
852 };
853
854 if AssertUnwindSafe(handler(context))
855 .catch_unwind()
856 .await
857 .is_err()
858 {
859 tracing::error!("server connected handler panicked");
860 }
861 }
862 }
863
864 async fn notify_disconnected(
865 &self,
866 connection_id: &str,
867 peer_addr: Option<std::net::SocketAddr>,
868 reason: String,
869 ) {
870 let handlers = self.disconnect_handlers.clone();
871 for handler in handlers {
872 let context = ServerDisconnectContext {
873 connection_id: connection_id.to_string(),
874 peer_addr,
875 reason: reason.clone(),
876 server: self.handle(),
877 };
878
879 if AssertUnwindSafe(handler(context))
880 .catch_unwind()
881 .await
882 .is_err()
883 {
884 tracing::error!("server disconnected handler panicked");
885 }
886 }
887 }
888
889 fn disconnect_reason(result: &Result<(), ServerError>) -> String {
890 match result {
891 Ok(()) => "connection closed".to_string(),
892 Err(ServerError::IdleTimeout(_)) => "idle timeout".to_string(),
893 Err(error) => error.to_string(),
894 }
895 }
896
897 async fn map_exception(&self, context: ExceptionContext) -> ErrorPayload {
898 match &self.exception_handler {
899 Some(handler) => handler(context).await,
900 None => context.error.into_payload(),
901 }
902 }
903}