socketioxide/lib.rs
1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![doc(
3 html_logo_url = "https://raw.githubusercontent.com/Totodore/socketioxide/refs/heads/main/.github/logo_dark.svg"
4)]
5#![doc(
6 html_favicon_url = "https://raw.githubusercontent.com/Totodore/socketioxide/refs/heads/main/.github/logo_dark.ico"
7)]
8#![warn(
9 clippy::all,
10 clippy::todo,
11 clippy::empty_enum,
12 clippy::mem_forget,
13 clippy::unused_self,
14 clippy::filter_map_next,
15 clippy::needless_continue,
16 clippy::needless_borrow,
17 clippy::match_wildcard_for_single_variants,
18 clippy::if_let_mutex,
19 clippy::await_holding_lock,
20 clippy::imprecise_flops,
21 clippy::suboptimal_flops,
22 clippy::lossy_float_literal,
23 clippy::rest_pat_in_fully_bound_structs,
24 clippy::fn_params_excessive_bools,
25 clippy::exit,
26 clippy::inefficient_to_string,
27 clippy::linkedlist,
28 clippy::macro_use_imports,
29 clippy::option_option,
30 clippy::verbose_file_reads,
31 clippy::unnested_or_patterns,
32 rust_2018_idioms,
33 rust_2024_compatibility,
34 future_incompatible,
35 nonstandard_style,
36 missing_docs
37)]
38//! Socketioxide is a socket.io server implementation that works as a tower layer/service.
39//! It integrates nicely with the rest of the [`tower`](https://docs.rs/tower/latest/tower/)/[`tokio`]/[`hyper`](https://docs.rs/hyper/latest/hyper/) ecosystem.
40//!
41//! ## Table of contents
42//! * [Features](#features)
43//! * [Compatibility](#compatibility)
44//! * [Usage](#usage)
45//! * [Initialisation](#initialisation)
46//! * [Handlers](#handlers)
47//! * [Extractors](#extractors)
48//! * [Events](#events)
49//! * [Middlewares](#middlewares)
50//! * [Emiting data](#emiting-data)
51//! * [Acknowledgements](#acknowledgements)
52//! * [State management](#state-management)
53//! * [Adapters](#adapters)
54//! * [Parsers](#parsers)
55//! * [Feature flags](#feature-flags)
56//!
57//! ## Features
58//! * Easy to use flexible axum-like API
59//! * Fully compatible with the official [socket.io client](https://socket.io/docs/v4/client-api/)
60//! * Support for the previous version of the protocol (v4).
61//! * State Management
62//! * Namespaces
63//! * Rooms
64//! * Acknowledgements
65//! * Common and Msgpack parsers
66//! * Polling & Websocket transports
67//!
68//! ## Compatibility
69//! Because it works as a tower [`layer`](tower_layer::Layer)/[`service`](tower_service::Service) or an hyper [`service`](hyper::service::Service)
70//! you can use it with any http server frameworks that works with tower/hyper:
71//! * [Axum](https://docs.rs/axum/latest/axum/)
72//! * [Warp](https://docs.rs/warp/latest/warp/) (Not supported with socketioxide >= 0.9.0 as long as warp doesn't migrate to hyper v1)
73//! * [Hyper](https://docs.rs/hyper/latest/hyper/)
74//! * [Salvo](https://docs.rs/salvo/latest/salvo/)
75//!
76//! Check the [examples](http://github.com/totodore/socketioxide/tree/main/examples) for
77//! more details on frameworks integration.
78//!
79//! ## Usage
80//! The API tries to mimic the equivalent JS API as much as possible.
81//! The main difference is that the default namespace `/` is not created automatically,
82//! you need to create it manually.
83//!
84//! #### Basic example with axum:
85//! ```no_run
86//! use axum::routing::get;
87//! use socketioxide::{
88//! extract::SocketRef,
89//! SocketIo,
90//! };
91//! #[tokio::main]
92//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
93//! let (layer, io) = SocketIo::new_layer();
94//!
95//! // Register a handler for the default namespace
96//! io.ns("/", async |s: SocketRef| {
97//! // For each "message" event received, send a "message-back" event with the "Hello World!" event
98//! s.on("message", async |s: SocketRef| {
99//! s.emit("message-back", "Hello World!").ok();
100//! });
101//! });
102//!
103//! let app = axum::Router::new()
104//! .route("/", get(async || "Hello, World!"))
105//! .layer(layer);
106//!
107//! let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
108//! axum::serve(listener, app).await.unwrap();
109//!
110//! Ok(())
111//! }
112//! ```
113//! ## Initialisation
114//! The [`SocketIo`] struct is the main entry point of the library. It is used to create
115//! a [`Layer`](tower_layer::Layer) or a [`Service`](tower_service::Service).
116//! Later it can be used as the equivalent of the `io` object in the JS API.
117//!
118//! When creating your [`SocketIo`] instance, you can use the builder pattern to configure it with the [`SocketIoBuilder`] struct.
119//! * See the [`SocketIoBuilder`] doc for more details on the available configuration options.
120//! * See the [`layer`] module doc for more details on layers.
121//! * See the [`service`] module doc for more details on services.
122//!
123//! #### Tower layer example with custom configuration:
124//! ```
125//! use socketioxide::SocketIo;
126//! let (layer, io) = SocketIo::builder()
127//! .max_payload(10_000_000) // Max HTTP payload size of 10M
128//! .max_buffer_size(10_000) // Max number of packets in the buffer
129//! .build_layer();
130//! ```
131//!
132//! #### Tower _standalone_ service example with default configuration:
133//! ```
134//! use socketioxide::SocketIo;
135//! let (svc, io) = SocketIo::new_svc();
136//! ```
137//!
138//! ## Handlers
139//! Handlers are async functions or clonable async closures that are given to the `io.ns`, the `socket.on` and the `socket.on_disconnect` fns.
140//! They can take from 0 to 16 arguments that implements the [`FromConnectParts`]
141//! trait for the [`ConnectHandler`], the [`FromMessageParts`] for
142//! the [`MessageHandler`] and the [`FromDisconnectParts`] for the [`DisconnectHandler`].
143//! They are greatly inspired by the axum handlers.
144//!
145//! A new task will be spawned for each incoming connection/message so it doesn't block the event management task.
146//!
147//! * Check the [`handler::connect`] module doc for more details on the connect handler and connect middlewares.
148//! * Check the [`handler::message`] module doc for more details on the message handler.
149//! * Check the [`handler::disconnect`] module doc for more details on the disconnect handler.
150//! * Check the [`extract`] module doc for more details on the extractors.
151//!
152//! ## Extractors
153//! Handlers params are called extractors and are used to extract data from the incoming connection/message. They are inspired by the axum extractors.
154//! An extractor is a struct that implements the [`FromConnectParts`] trait for the [`ConnectHandler`]
155//! the [`FromMessageParts`] for the [`MessageHandler`] and the
156//! [`FromDisconnectParts`] for the [`DisconnectHandler`].
157//!
158//! They can be used to extract data from the context of the handler and get specific params. Here are some examples of extractors:
159//! * [`Data`](extract::Data): extracts and deserialize from any receieved data, if a deserialization error occurs the handler won't be called:
160//! - for [`ConnectHandler`]: extracts and deserialize from the incoming auth data
161//! - for [`ConnectMiddleware`](handler::ConnectMiddleware): extract and deserialize from the incoming auth data.
162//! In case of error, the middleware chain stops and a `connect_error` event is sent.
163//! - for [`MessageHandler`]: extracts and deserialize from the incoming message data
164//! * [`TryData`](extract::TryData): extracts and deserialize from the any received data but with a `Result` type in case of error:
165//! - for [`ConnectHandler`] and [`ConnectMiddleware`](handler::ConnectMiddleware): extracts and deserialize from the incoming auth data
166//! - for [`MessageHandler`]: extracts and deserialize from the incoming message data
167//! * [`SocketRef`]: extracts a reference to the [`Socket`](socket::Socket)
168//! * [`AckSender`]: Can be used to send an ack response to the current message event
169//! * [`ProtocolVersion`]: extracts the protocol version of the socket
170//! * [`TransportType`]: extracts the transport type of the socket
171//! * [`DisconnectReason`](crate::socket::DisconnectReason): extracts the reason of the disconnection
172//! * [`State`]: extracts a [`Clone`] of a state previously set with [`SocketIoBuilder::with_state`](crate::io::SocketIoBuilder).
173//! * [`Extension`](extract::Extension): extracts a clone of the corresponding socket extension
174//! * [`MaybeExtension`](extract::MaybeExtension): extracts a clone of the corresponding socket extension if it exists
175//! * [`HttpExtension`](extract::HttpExtension): extracts a clone of the http request extension
176//! * [`MaybeHttpExtension`](extract::MaybeHttpExtension): extracts a clone of the http request extension if it exists
177//! * [`SocketIo`]: extracts a reference to the [`SocketIo`] handle
178//!
179//! ### Extractor order
180//! Extractors are run in the order of their declaration in the handler signature.
181//! If an extractor returns an error, the handler won't be called and a `tracing::error!` call
182//! will be emitted if the `tracing` feature is enabled.
183//!
184//! For the [`MessageHandler`], some extractors require to _consume_ the event and therefore
185//! only implement the [`FromMessage`](handler::FromMessage) trait.
186//!
187//! Note that any extractors that implement the [`FromMessageParts`] also implement by default
188//! the [`FromMessage`](handler::FromMessage) trait.
189//!
190//! ## Events
191//! There are three types of events:
192//! * The connect event is emitted when a new connection is established. It can be handled with the
193//! [`ConnectHandler`] and the `io.ns` method.
194//! * The message event is emitted when a new message is received. It can be handled with the
195//! [`MessageHandler`] and the `socket.on` method.
196//! * The disconnect event is emitted when a socket is closed. It can be handled with the
197//! [`DisconnectHandler`] and the `socket.on_disconnect` method.
198//!
199//! Only one handler can exist for an event so registering a new handler for an event will replace the previous one.
200//!
201//! ## Middlewares
202//! When providing a [`ConnectHandler`] for a namespace you can add any number of
203//! [`ConnectMiddleware`](handler::ConnectMiddleware) in front of it. It is useful to add authentication or logging middlewares.
204//!
205//! A middleware *must* return a `Result<(), E> where E: Display`.
206//! * If the result is `Ok(())`, the next middleware is called or if there is no more middleware,
207//! the socket is connected and the [`ConnectHandler`] is called.
208//! * If the result is an error, the namespace connection will be refused and the error will be returned with a
209//! [`connect_error` event and a `message`](https://socket.io/docs/v4/middlewares/#handling-middleware-error) field with the error.
210//!
211//! <div class="warning">
212//! Because the socket is not yet connected to the namespace,
213//! you can't send messages to it from the middleware.
214//! </div>
215//!
216//! See the [`handler::connect`](handler::connect#middleware) module doc for more details
217//! on middlewares and examples.
218//!
219//! ## [Emiting data](#emiting-data)
220//! Data can be emitted to a socket with the [`Socket::emit`](socket::Socket) method. It takes an event name and a data argument.
221//! The data argument can be any type that implements the [`serde::Serialize`] trait.
222//!
223//! You can emit from the [`SocketIo`] handle or the [`SocketRef`].
224//! The difference is that you can move the [`io`] handle everywhere because it is a cheaply cloneable struct.
225//! The [`SocketRef`] is a reference to the socket and you should avoid storing it in your own code (e.g. in HashMap/Vec).
226//! If you do so, you will have to remove the socket reference when the socket is disconnected to avoid memory leaks.
227//!
228//! Moreover the [`io`] handle can emit to any namespace while the [`SocketRef`] can only emit to the namespace of the socket.
229//!
230//! When using any `emit` fn, if you provide tuple-like data (tuple, arrays), it will be considered as multiple emit arguments.
231//! If you send a vector it will be considered as a single argument.
232//!
233//! #### Emitting binary data
234//! To emit binary data, you must use a data type that implements [`Serialize`] as binary data.
235//! Currently if you use `Vec<u8>` it will be considered as a number sequence and not binary data.
236//! To counter that you must either use a special type like [`Bytes`] or use the [`serde_bytes`] crate.
237//! If you want to emit generic binary data, use [`rmpv::Value`] rather than [`serde_json::Value`] otherwise
238//! the binary data will also be serialized as a number sequence.
239//!
240//! [`Serialize`]: serde::Serialize
241//! [`serde_bytes`]: https://docs.rs/serde_bytes
242//! [`Bytes`]: bytes::Bytes
243//! [`rmpv::Value`]: https://docs.rs/rmpv
244//! [`serde_json::Value`]: https://docs.rs/serde_json/latest/serde_json/value
245//!
246//! #### Emit errors
247//! If the data can't be serialized, a [`ParserError`] will be returned.
248//!
249//! If the socket is disconnected or the internal channel is full, a [`SendError`] will be returned.
250//! Moreover, a tracing log will be emitted if the `tracing` feature is enabled.
251//!
252//! #### Emitting with operators
253//! To configure the emit, you can chain [`Operators`](operators) methods to the emit call. With that you can easily configure the following options:
254//! * rooms: emit, join, leave to specific rooms
255//! * namespace: emit to a specific namespace (only from the [`SocketIo`] handle)
256//! * timeout: set a custom timeout when waiting for an ack
257//! * binary: emit a binary payload with the message
258//! * local: broadcast only to the current node (in case of a cluster)
259//!
260//! Check the [`operators`] module doc for more details on operators.
261//!
262//! ## Acknowledgements
263//! You can ensure that a message has been received by the client/server with acknowledgements.
264//!
265//! #### Server acknowledgements
266//! They are implemented with the [`AckSender`] extractor.
267//! You can send an ack response with an optional binary payload with the [`AckSender::send`] method.
268//! If the client doesn't send an ack id to respond to, the [`AckSender::send`] method will do nothing.
269//!
270//! #### Client acknowledgements
271//! If you want to emit/broadcast a message and await for a/many client(s) acknowledgment(s) you can use:
272//! * [`SocketRef::emit_with_ack`] for a single client
273//! * [`BroadcastOperators::emit_with_ack`] for broadcasting or [emit configuration](#emiting-data).
274//! * [`SocketIo::emit_with_ack`] for broadcasting to an entire namespace.
275//!
276//! [`SocketRef::emit_with_ack`]: crate::extract::SocketRef#method.emit_with_ack
277//! [`BroadcastOperators::emit_with_ack`]: crate::operators::BroadcastOperators#method.emit_with_ack
278//! [`SocketIo::emit_with_ack`]: SocketIo#method.emit_with_ack
279//! [`AckStream`]: crate::ack::AckStream
280//! [`ParserError`]: crate::parser::ParserError
281//!
282//! ## [State management](#state-management)
283//! There are two ways to manage the state of the server:
284//!
285//! #### Per socket state
286//! You can enable the `extensions` feature and use the [`extensions`](socket::Socket::extensions) field on any socket to manage
287//! the state of each socket. It is backed by a [`RwLock<HashMap>>`](std::sync::RwLock) so you can safely access it
288//! from multiple threads. However, the value must be [`Clone`] and `'static`.
289//! When calling get, or using the [`Extension`](extract::Extension)/[`MaybeExtension`](extract::MaybeExtension) extractor,
290//! the value will always be cloned.
291//! See the [`extensions`] module doc for more details.
292//!
293//! #### Global state
294//! You can enable the `state` feature and use [`SocketIoBuilder::with_state`](SocketIoBuilder) method to set
295//! multiple global states for the server. You can then access them from any handler with the [`State`] extractor.
296//!
297//! The state is stored in the [`SocketIo`] handle and is shared between all the sockets. The only limitation is that all the
298//! provided state types must be clonable.
299//! Therefore it is recommended to use the [`Arc`](std::sync::Arc) type to share the state between the handlers.
300//!
301//! You can then use the [`State`] extractor to access the state in the handlers.
302//!
303//! ## Adapters
304//! This library is designed to support clustering through the use of adapters.
305//! Adapters enable broadcasting messages and managing socket room memberships across nodes
306//! without requiring changes to your code. The [`Adapter`] trait abstracts the underlying system,
307//! making it easy to integrate with different implementations.
308//!
309//! Adapters typically interact with third-party systems like Redis, Postgres, Kafka, etc.,
310//! to facilitate message exchange between nodes.
311//!
312//! The default adapter is the [`LocalAdapter`], a simple in-memory implementation. If you intend
313//! to use a different adapter, ensure that extractors are either generic over the adapter type
314//! or explicitly specify the adapter type for each extractor that requires it.
315//!
316//! #### Write this:
317//! ```
318//! # use socketioxide::{SocketIo, adapter::Adapter, extract::SocketRef};
319//! async fn my_handler<A: Adapter>(s: SocketRef<A>, io: SocketIo<A>) { }
320//! let (layer, io) = SocketIo::new_layer();
321//! io.ns("/", my_handler);
322//! ```
323//! #### Instead of that:
324//! ```
325//! # use socketioxide::{SocketIo, adapter::Adapter, extract::SocketRef};
326//! async fn my_handler(s: SocketRef, io: SocketIo) { }
327//! let (layer, io) = SocketIo::new_layer();
328//! io.ns("/", my_handler);
329//! ```
330//!
331//! Refer to the [README](https://github.com/totodore/socketioxide) for a list of available adapters and
332//! the [examples](https://github.com/totodore/socketioxide/tree/main/examples) for detailed usage guidance.
333//! You can also consult specific adapter crate documentation for more information.
334//!
335//! ## Parsers
336//! This library uses the socket.io common parser which is the default for all the socket.io implementations.
337//! Socketioxide also provided a msgpack parser. It is faster and more efficient than the default parser especially
338//! for binary data or payloads with a lot of numbers.
339//! To enable it, you must enable the [`msgpack`](#feature-flags) feature and then use the
340//! [`with_parser`](SocketIoBuilder#method.with_parser) fn to set the parser to [`ParserConfig::msgpack`](ParserConfig#method.msgpack).
341//!
342//! ## [Feature flags](#feature-flags)
343//! * `v4`: enable support for the socket.io protocol v4
344//! * `tracing`: enable logging with [`tracing`] calls
345//! * `extensions`: enable per-socket state with the [`extensions`] module
346//! * `state`: enable global state management
347//! * `msgpack`: enable msgpack custom parser
348//!
349//! [`Adapter`]: adapter::Adapter
350//! [`LocalAdapter`]: adapter::LocalAdapter
351//! [`SocketRef`]: extract::SocketRef
352//! [`State`]: extract::State
353//! [`Data`]: extract::Data
354//! [`TryData`]: extract::TryData
355//! [`ConnectHandler`]: handler::ConnectHandler
356//! [`Ì€ConnectMiddleware`]: handler::ConnectMiddleware
357//! [`MessageHandler`]: handler::MessageHandler
358//! [`DisconnectHandler`]: handler::DisconnectHandler
359//! [`FromMessage`]: handler::FromMessage
360//! [`FromMessageParts`]: handler::FromMessageParts
361//! [`FromDisconnectParts`]: handler::FromDisconnectParts
362//! [`FromConnectParts`]: handler::FromConnectParts
363//! [`AckSender`]: extract::AckSender
364//! [`AckSender::send`]: extract::AckSender#method.send
365//! [`io`]: SocketIo
366
367#[cfg_attr(docsrs, doc(cfg(feature = "extensions")))]
368#[cfg(feature = "extensions")]
369pub mod extensions;
370
371pub mod ack;
372pub mod adapter;
373pub mod extract;
374pub mod handler;
375pub mod layer;
376pub mod operators;
377pub mod service;
378pub mod socket;
379
380pub use engineioxide::TransportType;
381pub use errors::{
382 AckError, AdapterError, BroadcastError, EmitWithAckError, NsInsertError, ParserError,
383 SendError, SocketError,
384};
385pub use io::{ParserConfig, SocketIo, SocketIoBuilder, SocketIoConfig};
386
387mod client;
388mod errors;
389mod io;
390mod ns;
391mod parser;
392
393/// Socket.IO protocol version.
394/// It is accessible with the [`Socket::protocol`](socket::Socket) method or as an extractor
395///
396/// **Note**: The socket.io protocol version does not correspond to the engine.io protocol version.
397#[derive(Debug, Copy, Clone, PartialEq)]
398pub enum ProtocolVersion {
399 /// The socket.io protocol version 4, only available with the feature flag `v4`
400 V4 = 4,
401 /// The socket.io protocol version 5, enabled by default
402 V5 = 5,
403}
404
405impl From<ProtocolVersion> for engineioxide::ProtocolVersion {
406 fn from(value: ProtocolVersion) -> Self {
407 match value {
408 ProtocolVersion::V4 => Self::V3,
409 ProtocolVersion::V5 => Self::V4,
410 }
411 }
412}
413impl From<engineioxide::ProtocolVersion> for ProtocolVersion {
414 fn from(value: engineioxide::ProtocolVersion) -> Self {
415 match value {
416 engineioxide::ProtocolVersion::V3 => Self::V4,
417 engineioxide::ProtocolVersion::V4 => Self::V5,
418 }
419 }
420}