Expand description
Socketioxide is a socket.io server implementation that works as a tower layer/service.
It integrates nicely with the rest of the tower
/tokio
/hyper
ecosystem.
§Table of contents
- Features
- Compatibility
- Usage
- Initialisation
- Handlers
- Extractors
- Events
- Middlewares
- Emiting data
- Acknowledgements
- State management
- Adapters
- Parsers
- Feature flags
§Features
- Easy to use flexible axum-like API
- Fully compatible with the official socket.io client
- Support for the previous version of the protocol (v4).
- State Management
- Namespaces
- Rooms
- Acknowledgements
- Common and Msgpack parsers
- Polling & Websocket transports
§Compatibility
Because it works as a tower layer
/service
or an hyper service
you can use it with any http server frameworks that works with tower/hyper:
- Axum
- Warp (Not supported with socketioxide >= 0.9.0 as long as warp doesn’t migrate to hyper v1)
- Hyper
- Salvo
Check the examples for more details on frameworks integration.
§Usage
The API tries to mimic the equivalent JS API as much as possible.
The main difference is that the default namespace /
is not created automatically,
you need to create it manually.
§Basic example with axum:
use axum::routing::get;
use socketioxide::{
extract::SocketRef,
SocketIo,
};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let (layer, io) = SocketIo::new_layer();
// Register a handler for the default namespace
io.ns("/", |s: SocketRef| {
// For each "message" event received, send a "message-back" event with the "Hello World!" event
s.on("message", |s: SocketRef| {
s.emit("message-back", "Hello World!").ok();
});
});
let app = axum::Router::new()
.route("/", get(|| async { "Hello, World!" }))
.layer(layer);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
Ok(())
}
§Initialisation
The SocketIo
struct is the main entry point of the library. It is used to create
a Layer
or a Service
.
Later it can be used as the equivalent of the io
object in the JS API.
When creating your SocketIo
instance, you can use the builder pattern to configure it with the SocketIoBuilder
struct.
- See the
SocketIoBuilder
doc for more details on the available configuration options. - See the
layer
module doc for more details on layers. - See the
service
module doc for more details on services.
§Tower layer example with custom configuration:
use socketioxide::SocketIo;
let (layer, io) = SocketIo::builder()
.max_payload(10_000_000) // Max HTTP payload size of 10M
.max_buffer_size(10_000) // Max number of packets in the buffer
.build_layer();
§Tower standalone service example with default configuration:
use socketioxide::SocketIo;
let (svc, io) = SocketIo::new_svc();
§Handlers
Handlers are functions or clonable closures that are given to the io.ns
, the socket.on
and the socket.on_disconnect
fns.
They can be async or sync and can take from 0 to 16 arguments that implements the FromConnectParts
trait for the ConnectHandler
, the FromMessageParts
for
the MessageHandler
and the FromDisconnectParts
for the DisconnectHandler
.
They are greatly inspired by the axum handlers.
If they are async, a new task will be spawned for each incoming connection/message so it doesn’t block the event management task.
- Check the
handler::connect
module doc for more details on the connect handler and connect middlewares. - Check the
handler::message
module doc for more details on the message handler. - Check the
handler::disconnect
module doc for more details on the disconnect handler. - Check the
extract
module doc for more details on the extractors.
§Extractors
Handlers params are called extractors and are used to extract data from the incoming connection/message. They are inspired by the axum extractors.
An extractor is a struct that implements the FromConnectParts
trait for the ConnectHandler
the FromMessageParts
for the MessageHandler
and the
FromDisconnectParts
for the DisconnectHandler
.
They can be used to extract data from the context of the handler and get specific params. Here are some examples of extractors:
Data
: extracts and deserialize from any receieved data, if a deserialization error occurs the handler won’t be called:- for
ConnectHandler
: extracts and deserialize from the incoming auth data - for
ConnectMiddleware
: extract and deserialize from the incoming auth data. In case of error, the middleware chain stops and aconnect_error
event is sent. - for
MessageHandler
: extracts and deserialize from the incoming message data
- for
TryData
: extracts and deserialize from the any received data but with aResult
type in case of error:- for
ConnectHandler
andConnectMiddleware
: extracts and deserialize from the incoming auth data - for
MessageHandler
: extracts and deserialize from the incoming message data
- for
SocketRef
: extracts a reference to theSocket
AckSender
: Can be used to send an ack response to the current message eventProtocolVersion
: extracts the protocol version of the socketTransportType
: extracts the transport type of the socketDisconnectReason
: extracts the reason of the disconnectionState
: extracts aClone
of a state previously set withSocketIoBuilder::with_state
.Extension
: extracts a clone of the corresponding socket extensionMaybeExtension
: extracts a clone of the corresponding socket extension if it existsHttpExtension
: extracts a clone of the http request extensionMaybeHttpExtension
: extracts a clone of the http request extension if it existsSocketIo
: extracts a reference to theSocketIo
handle
§Extractor order
Extractors are run in the order of their declaration in the handler signature.
If an extractor returns an error, the handler won’t be called and a tracing::error!
call
will be emitted if the tracing
feature is enabled.
For the MessageHandler
, some extractors require to consume the event and therefore
only implement the FromMessage
trait.
Note that any extractors that implement the FromMessageParts
also implement by default
the FromMessage
trait.
§Events
There are three types of events:
- The connect event is emitted when a new connection is established. It can be handled with the
ConnectHandler
and theio.ns
method. - The message event is emitted when a new message is received. It can be handled with the
MessageHandler
and thesocket.on
method. - The disconnect event is emitted when a socket is closed. It can be handled with the
DisconnectHandler
and thesocket.on_disconnect
method.
Only one handler can exist for an event so registering a new handler for an event will replace the previous one.
§Middlewares
When providing a ConnectHandler
for a namespace you can add any number of
ConnectMiddleware
in front of it. It is useful to add authentication or logging middlewares.
A middleware must return a Result<(), E> where E: Display
.
- If the result is
Ok(())
, the next middleware is called or if there is no more middleware, the socket is connected and theConnectHandler
is called. - If the result is an error, the namespace connection will be refused and the error will be returned with a
connect_error
event and amessage
field with the error.
See the handler::connect
module doc for more details
on middlewares and examples.
§Emiting data
Data can be emitted to a socket with the Socket::emit
method. It takes an event name and a data argument.
The data argument can be any type that implements the serde::Serialize
trait.
You can emit from the SocketIo
handle or the SocketRef
.
The difference is that you can move the io
handle everywhere because it is a cheaply cloneable struct.
The SocketRef
is a reference to the socket and you should avoid storing it in your own code (e.g. in HashMap/Vec).
If you do so, you will have to remove the socket reference when the socket is disconnected to avoid memory leaks.
Moreover the io
handle can emit to any namespace while the SocketRef
can only emit to the namespace of the socket.
When using any emit
fn, if you provide tuple-like data (tuple, arrays), it will be considered as multiple emit arguments.
If you send a vector it will be considered as a single argument.
§Emitting binary data
To emit binary data, you must use a data type that implements Serialize
as binary data.
Currently if you use Vec<u8>
it will be considered as a number sequence and not binary data.
To counter that you must either use a special type like Bytes
or use the serde_bytes
crate.
If you want to emit generic binary data, use rmpv::Value
rather than serde_json::Value
otherwise
the binary data will also be serialized as a number sequence.
§Emit errors
If the data can’t be serialized, a ParserError
will be returned.
If the socket is disconnected or the internal channel is full, a SendError
will be returned.
Moreover, a tracing log will be emitted if the tracing
feature is enabled.
§Emitting with operators
To configure the emit, you can chain Operators
methods to the emit call. With that you can easily configure the following options:
- rooms: emit, join, leave to specific rooms
- namespace: emit to a specific namespace (only from the
SocketIo
handle) - timeout: set a custom timeout when waiting for an ack
- binary: emit a binary payload with the message
- local: broadcast only to the current node (in case of a cluster)
Check the operators
module doc for more details on operators.
§Acknowledgements
You can ensure that a message has been received by the client/server with acknowledgements.
§Server acknowledgements
They are implemented with the AckSender
extractor.
You can send an ack response with an optional binary payload with the AckSender::send
method.
If the client doesn’t send an ack id to respond to, the AckSender::send
method will do nothing.
§Client acknowledgements
If you want to emit/broadcast a message and await for a/many client(s) acknowledgment(s) you can use:
SocketRef::emit_with_ack
for a single clientBroadcastOperators::emit_with_ack
for broadcasting or emit configuration.SocketIo::emit_with_ack
for broadcasting to an entire namespace.
§State management
There are two ways to manage the state of the server:
§Per socket state
You can enable the extensions
feature and use the extensions
field on any socket to manage
the state of each socket. It is backed by a RwLock<HashMap>>
so you can safely access it
from multiple threads. However, the value must be Clone
and 'static
.
When calling get, or using the Extension
/MaybeExtension
extractor,
the value will always be cloned.
See the extensions
module doc for more details.
§Global state
You can enable the state
feature and use SocketIoBuilder::with_state
method to set
multiple global states for the server. You can then access them from any handler with the State
extractor.
The state is stored in the SocketIo
handle and is shared between all the sockets. The only limitation is that all the
provided state types must be clonable.
Therefore it is recommended to use the Arc
type to share the state between the handlers.
You can then use the State
extractor to access the state in the handlers.
§Adapters
This library is designed to support clustering through the use of adapters.
Adapters enable broadcasting messages and managing socket room memberships across nodes
without requiring changes to your code. The Adapter
trait abstracts the underlying system,
making it easy to integrate with different implementations.
Adapters typically interact with third-party systems like Redis, Postgres, Kafka, etc., to facilitate message exchange between nodes.
The default adapter is the LocalAdapter
, a simple in-memory implementation. If you intend
to use a different adapter, ensure that extractors are either generic over the adapter type
or explicitly specify the adapter type for each extractor that requires it.
§Write this:
fn my_handler<A: Adapter>(s: SocketRef<A>, io: SocketIo<A>) { }
let (layer, io) = SocketIo::new_layer();
io.ns("/", my_handler);
§Instead of that:
fn my_handler(s: SocketRef, io: SocketIo) { }
let (layer, io) = SocketIo::new_layer();
io.ns("/", my_handler);
Refer to the README for a list of available adapters and the examples for detailed usage guidance. You can also consult specific adapter crate documentation for more information.
§Parsers
This library uses the socket.io common parser which is the default for all the socket.io implementations.
Socketioxide also provided a msgpack parser. It is faster and more efficient than the default parser especially
for binary data or payloads with a lot of numbers.
To enable it, you must enable the msgpack
feature and then use the
with_parser
fn to set the parser to ParserConfig::msgpack
.
§Feature flags
v4
: enable support for the socket.io protocol v4tracing
: enable logging withtracing
callsextensions
: enable per-socket state with theextensions
modulestate
: enable global state managementmsgpack
: enable msgpack custom parser
Modules§
- ack
- Acknowledgement related types and functions.
- adapter
- Adapters are responsible for managing the internal state of the server (rooms, sockets, etc…).
When a socket joins or leaves a room, the adapter is responsible for updating the state.
The default adapter is the
LocalAdapter
, which stores the state in memory. Other adapters can be made to share the state between multiple servers. - extensions
extensions
Extensions
used to store extra data in each socket instance.- extract
- Extractors for
ConnectHandler
,ConnectMiddleware
,MessageHandler
andDisconnectHandler
. - handler
- Functions and types used to handle incoming connections and messages. There is three main types of handlers: connect, message and disconnect. All handlers can be async or not.
- layer
- A tower
Layer
for socket.io so it can be used as a middleware with frameworks supporting layers. - operators
- Operators are used to select sockets to send a packet to, or to configure the packet that will be emitted.
- service
- A Tower
Service
and HyperService
for socket.io so it - socket
- A
Socket
represents a client connected to a namespace. The socket struct itself should not be used directly, but through aSocketRef
.
Structs§
- Adapter
Error - Error type for the
CoreAdapter
trait. - Parser
Config - The parser to use to encode and decode socket.io packets
- Parser
Error - A parser error that wraps any error that can occur during parsing.
- Socket
Io - The
SocketIo
instance can be cheaply cloned and moved around everywhere in your program. It can be used as the main handle to access the whole socket.io context. - Socket
IoBuilder - A builder to create a
SocketIo
instance. It contains everything to configure the socket.io server with aSocketIoConfig
. It can be used to build either a TowerLayer
or aService
. - Socket
IoConfig - Configuration for Socket.IO & Engine.IO
Enums§
- AckError
- Error type for ack operations.
- Broadcast
Error - Error type for broadcast operations.
- Emit
With AckError - Error type for the
emit_with_ack
method. - NsInsert
Error - Represents errors that can occur when inserting a new route.
- Protocol
Version - Socket.IO protocol version.
It is accessible with the
Socket::protocol
method or as an extractor - Send
Error - Error type for sending operations.
- Socket
Error - Error type when using the underlying engine.io socket
- Transport
Type - The type of
transport
used by the client.