Crate socketioxide
source ·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
- Emiting data
- Acknowledgements
- Adapters
- 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).
- Namespaces
- Rooms
- Acknowledgements
- Polling & Websocket transports
Compatibility
Because it works as a tower layer/service you can use it with any http server frameworks that works with tower/hyper:
Note that for v1 of hyper and salvo, you need to enable the hyper-v1 feature and call with_hyper_v1 on your layer/service.
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 axum::Server;
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);
Server::bind(&"127.0.0.1:3000".parse().unwrap())
.serve(app.into_make_service())
.await?;
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
SocketIoBuilderdoc for more details on the available configuration options. - See the
layermodule doc for more details on layers. - See the
servicemodule doc for more details on services. - See the
hyper_v1module doc for more details on hyper v1 compatibility.
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();Tower service example with hyper v1 (requires the hyper-v1 feature) and default configuration:
use socketioxide::SocketIo;
let (svc, io) = SocketIo::new_svc();
let svc = svc.with_hyper_v1();Tower layer example with hyper v1 (requires the hyper-v1 feature) and default configuration:
use socketioxide::SocketIo;
let (layer, io) = SocketIo::new_layer();
let layer = layer.with_hyper_v1();Handlers
Handlers are functions or clonable closures that are given to the io.ns and the socket.on methods. They can be async or sync and can take from 0 to 16 arguments that implements the FromConnectParts trait for the ConnectHandler and the FromMessageParts for the MessageHandler. 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::connectmodule doc for more details on the connect handler - Check the
handler::messagemodule doc for more details on the message handler. - Check the
handler::extractmodule 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 and the FromMessageParts for the MessageHandler.
Here are some examples of extractors:
Data: extracts and deserialize to json any data, if a deserialize error occurs the handler won’t be called- for
ConnectHandler: extracts and deserialize to json the auth data - for
MessageHandler: extracts and deserialize to json the message data
- for
TryData: extracts and deserialize to json any data but with aResulttype in case of error- for
ConnectHandler: extracts and deserialize to json the auth data - for
MessageHandler: extracts and deserialize to json the message data
- for
SocketRef: extracts a reference to theSocketBin: extract a binary payload for a given message. Because it consumes the event it should be the last argumentAckSender: Can be used to send an ack response to the current message event
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, like the Bin extractor, therefore they should be the last argument.
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
ConnectHandlerand theio.nsmethod. - The message event is emitted when a new message is received. It can be handled with the
MessageHandlerand thesocket.onmethod. - The disconnect event is emitted when a socket is closed. Contrary to the two previous events, the callback is not flexible, it must be async and have the following signature
async fn(SocketRef, DisconnectReason). It can be handled with thesocket.on_disconnectmethod.
Only one handler can exist for an event so registering a new handler for an event will replace the previous one.
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 cannot be cloned.
Moreover the io handle can emit to any namespace while the SocketRef can only emit to the namespace of the socket.
Emit errors
If the data can’t be serialized to json, an serde_json::Error will be returned.
If the socket is disconnected or the internal channel is full, a tracing log will be emitted if the tracing feature is enabled and the message will be dropped.
This solution is not ideal and will be improved in the future (see socketioxide/172).
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
SocketIohandle) - 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::Operators doc for more details on the 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 response, the AckSender::send method will do nothing.
Client acknowledgements
You can use the Socket::emit_with_ack method to emit a message with an ack callback.
It will return a Future that will resolve when the acknowledgement is received.
Adapters
This library is designed to work with clustering. It uses the Adapter trait to abstract the underlying storage.
By default it uses the LocalAdapter which is a simple in-memory adapter.
Currently there is no other adapters available but more will be added in the future.
Feature flags
hyper-v1: enable support for hyper v1v4: enable support for the socket.io protocol v4tracing: enable logging withtracingcallsextensions: enableextensionsmodule
Re-exports
pub use handler::extract;
Modules
- Adapters are responsible for managing the state of the server. 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
extensionsExtensionsused to store extra data in each socket instance. - Functions and types used to handle incoming connections and messages. There is two main types of handlers:
ConnectHandlerandMessageHandler. Both handlers can be async or not. - hyper_v1
hyper-v1A Hyper v1Servicefor socket.io so it can be used with frameworks working with hyper v1 - A tower [
Layer] for socket.io so it can be used as a middleware with frameworks supporting layers. Operatorsare used to select sockets to send a packet to, or to configure the packet that will be emitted. It uses the builder pattern to chain operators.- A tower [
Service] for socket.io so it can be used with frameworks supporting tower services.
Structs
- The
SocketIoinstance 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. - A builder to create a
SocketIoinstance. It contains everything to configure the socket.io server with aSocketIoConfig. It can be used to build either a TowerLayeror aService. - Configuration for Socket.IO & Engine.IO
Enums
- Error type for ack responses
- Error type for broadcast operations.
- Socket.IO protocol version
- Error type for sending operations.
- The type of
transportused by the client.