High-performance, scalable WebSocket server built in Rust π¦
Sockudo is a production-ready WebSocket server that provides real-time communication capabilities with built-in support for channels, presence, authentication, rate limiting, and horizontal scaling. Designed for modern applications that need reliable, fast, and scalable real-time features. It aims for Pusher protocol compatibility, making it a potential backend for clients like Laravel Echo.
- High Performance: Built in Rust for maximum speed and efficiency.
- WebSocket Protocol: Full RFC 6455 compliance.
- Channel System: Organize connections with public, private, and presence channels. Support for private-encrypted channels is also included.
- Real-time Messaging: Instant bidirectional communication.
- Horizontal Scaling: Multi-node clustering with adapters for Redis, Redis Cluster, and NATS.
- Channel Authentication: Secure private and presence channels using token-based authentication.
- User Authentication: User sign-in support with custom data, typically validated with signatures.
- Rate Limiting: Configurable limits for API and WebSocket connections, with backends like Memory or Redis.
- SSL/TLS Support: Production-ready HTTPS and WSS.
- CORS Configuration: Flexible cross-origin request handling.
- Prometheus Metrics: Comprehensive performance monitoring for connections, messages, API calls, and adapter performance.
- Health Checks: Built-in health endpoints (
/up/{appId}
) for load balancers. - Structured Logging: Configurable logging with multiple levels (DEBUG/INFO).
- Debug Mode: Enhanced debugging for development.
- Multiple Adapters: Choose between Local (single-node), Redis, Redis Cluster, or NATS for message broadcasting and horizontal scaling.
- Database Support for App Management: Options include Memory, MySQL, PostgreSQL, and DynamoDB for storing application configurations.
- Queue Systems: Supports Memory, Redis, Redis Cluster, and SQS for background job processing (e.g., webhooks).
- Cache Backends: Options for Memory, Redis, or Redis Cluster for caching channel data and other information.
- Webhooks: Real-time event notifications (e.g.,
channel_occupied
,channel_vacated
,member_added
,member_removed
,client_event
) to your services. - AWS Lambda: Direct Lambda function invocation for webhooks.
- REST API: Full HTTP API for triggering events, batch events, and managing channels/users.
- Client Libraries: Compatible with Pusher client libraries due to protocol adherence.
# Clone the repository
git clone https://github.com/RustNSparks/sockudo.git
cd sockudo
# Ensure you have an .env file (e.g., from .env.example)
# cp .env.example .env
# nano .env # Customize if needed
# Quick setup and start (assumes make command is configured)
make quick-start
# Or, using docker-compose directly:
# docker-compose up -d
# you can also use
docker pull sockudo/sockudo:latest
Sockudo should now be running. Default endpoints (configurable):
- WebSocket Server:
ws://localhost:6001/app/your-app-key
(e.g.,demo-key
if using default app) - REST API:
http://localhost:6001
(e.g.,http://localhost:6001/apps/demo-app/events
) - Metrics:
http://localhost:9601/metrics
- Health Check:
http://localhost:6001/up/your-app-id
- Rust 1.85+ (
rustup install stable
) - Redis (optional, for scaling and certain drivers)
- MySQL/PostgreSQL (optional, for persistent app storage if using those drivers)
# Install using cargo
cargo install sockudo
# Or install using cargo-binstall (faster)
cargo binstall sockudo
# Run Sockudo (ensure config/config.json exists or use ENV variables)
sockudo --config config/config.json
# Clone the repository
git clone [https://github.com/RustNSparks/sockudo.git](https://github.com/RustNSparks/sockudo.git)
cd sockudo
# Build the project
cargo build --release
# Run with a configuration file (recommended)
./target/release/sockudo --config config/config.json
# Or rely on environment variables and default app settings
# ./target/release/sockudo
// Connect to a public channel
const ws = new WebSocket('ws://localhost:6001/app/your-app-key'); // Replace your-app-key
ws.onopen = () => {
console.log('Connected to Sockudo!');
// Subscribe to a channel
ws.send(JSON.stringify({
event: 'pusher:subscribe',
data: { channel: 'my-channel' }
}));
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Received:', data);
// Example: Handle connection established
if (data.event === 'pusher:connection_established') {
const socketId = JSON.parse(data.data).socket_id;
console.log('My Socket ID is:', socketId);
}
// Example: Handle subscription success
if (data.event === 'pusher_internal:subscription_succeeded' && data.channel === 'my-channel') {
console.log('Successfully subscribed to my-channel!');
}
};
ws.onerror = (error) => {
console.error('WebSocket Error:', error);
};
ws.onclose = () => {
console.log('Disconnected from Sockudo.');
};
Actual signature generation must happen on your backend.
const ws = new WebSocket('ws://localhost:6001/app/your-app-key');
ws.onopen = () => {
// Assume 'generatedAuthSignature' is obtained from your backend
// after authenticating the user for this channel and socket_id.
// See Security section for more details on signature generation.
const generatedAuthSignature = "your-app-key:backend_generated_signature_for_private_channel";
ws.send(JSON.stringify({
event: 'pusher:subscribe',
data: {
channel: 'private-orders',
auth: generatedAuthSignature
}
}));
};
Actual signature generation must happen on your backend.
const ws = new WebSocket('ws://localhost:6001/app/your-app-key');
ws.onopen = () => {
// Assume 'generatedAuthSignatureForPresence' is obtained from your backend.
// Channel data includes user_id and user_info for presence events.
const channelData = JSON.stringify({
user_id: 'user123',
user_info: { name: 'John Doe', avatar: 'avatar.jpg' }
});
const generatedAuthSignatureForPresence = "your-app-key:backend_generated_signature_for_presence_channel";
ws.send(JSON.stringify({
event: 'pusher:subscribe',
data: {
channel: 'presence-chat',
auth: generatedAuthSignatureForPresence,
channel_data: channelData
}
}));
};
// Listen for presence events
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.event === 'pusher_internal:member_added' && message.channel === 'presence-chat') {
console.log('Member added:', message.data);
}
if (message.event === 'pusher_internal:member_removed' && message.channel === 'presence-chat') {
console.log('Member removed:', message.data);
}
};
Requires authentication (see Security section).
# Send event to a channel
# Replace your-app-id, your-app-key, and generate a valid auth_signature
curl -X POST "http://localhost:6001/apps/your-app-id/events?auth_key=your-app-key&auth_timestamp=$(date +%s)&auth_version=1.0&auth_signature=your_generated_signature&body_md5=$(echo -n '{"name":"order-update","channels":["private-orders"],"data":{"order_id":123,"status":"shipped"}}' | md5sum | awk '{print $1}')" \
-H "Content-Type: application/json" \
-d '{
"name": "order-update",
"channels": ["private-orders"],
"data": {"order_id": 123, "status": "shipped"}
}'
Client events can only be sent on private or presence channels by authenticated clients for whom the app has enable_client_messages
set to true
.
// Ensure the WebSocket 'ws' is connected and subscribed to 'presence-chat' (a private or presence channel)
ws.send(JSON.stringify({
event: 'client-message', // Must start with 'client-'
channel: 'presence-chat',
data: { message: 'Hello everyone!' }
}));
Sockudo can be configured via a JSON file (default: config/config.json
) or environment variables. Environment variables generally override file settings for basic options, but the file provides more detailed structure.
Create a .env
file (or set them in your environment):
# Basic Settings
HOST=0.0.0.0
PORT=6001
DEBUG=false # Enables more verbose logging
# Database (Example for Redis, adapt for MySQL/Postgres/DynamoDB if used as primary for AppManager)
REDIS_URL=redis://redis:6379/0 # Used by multiple components if not overridden
# For MySQL AppManager:
# DATABASE_MYSQL_HOST=mysql
# DATABASE_MYSQL_USER=sockudo
# DATABASE_MYSQL_PASSWORD=your_mysql_password
# DATABASE_MYSQL_DATABASE=sockudo
# Drivers (Lowercase: "local", "redis", "redis-cluster", "nats", "memory", "mysql", "pgsql", "dynamodb", "sqs")
ADAPTER_DRIVER=redis # For horizontal scaling
APP_MANAGER_DRIVER=memory # For app definitions
CACHE_DRIVER=redis # For caching
QUEUE_DRIVER=redis # For webhooks & background tasks
# Security
SSL_ENABLED=false # Set to true for production and provide paths
# SSL_CERT_PATH=/path/to/cert.pem
# SSL_KEY_PATH=/path/to/key.pem
# REDIS_PASSWORD=your-redis-password # If Redis requires auth
# Application Defaults (if not using a persistent AppManager or for fallback)
SOCKUDO_DEFAULT_APP_ID=demo-app
SOCKUDO_DEFAULT_APP_KEY=demo-key
SOCKUDO_DEFAULT_APP_SECRET=demo-secret
SOCKUDO_ENABLE_CLIENT_MESSAGES=true # For the default app
Refer to src/options.rs
and src/main.rs
for a comprehensive list of all ENV var overrides and their default behaviors.
Provides detailed control over all aspects. Below is a snippet; refer to your uploaded config/config.json
for the full structure.
{
"debug": false,
"host": "0.0.0.0",
"port": 6001,
"adapter": {
"driver": "redis", // "local", "redis", "redis-cluster", "nats"
"redis": { // Settings for 'redis' or 'redis-cluster' if chosen
"prefix": "sockudo_adapter:",
"cluster_mode": false // Set true if using redis-cluster via the 'redis' driver config
// "requests_timeout": 5000
},
"cluster": { // Specific settings if driver is 'redis-cluster'
// "nodes": ["redis://node1:7000", "redis://node2:7001"],
// "prefix": "sockudo_cluster_adapter:"
},
"nats": {
// "servers": ["nats://nats-server:4222"],
// "prefix": "sockudo_nats_adapter:"
}
},
"app_manager": {
"driver": "memory", // "memory", "mysql", "pgsql", "dynamodb"
"array": { // Used if driver is "memory" and you want to define apps in config
"apps": [
{
"id": "my-app-id",
"key": "my-app-key",
"secret": "my-app-secret",
"max_connections": 1000,
"enable_client_messages": true,
// ... other app-specific limits from src/app/config.rs
}
]
}
// database-specific app_manager configs would go under "database"
},
"cache": {
"driver": "redis", // "memory", "redis", "redis-cluster", "none"
"redis": {
// "prefix": "sockudo_cache:",
// "url_override": "redis://another-redis:6379"
},
"memory": {
// "ttl": 300, "max_capacity": 10000
}
},
"queue": {
"driver": "redis", // "memory", "redis", "redis-cluster", "sqs", "none"
"redis": {
// "concurrency": 5, "prefix": "sockudo_queue:"
},
"sqs": {
// "region": "us-east-1", "concurrency": 5
}
},
"rate_limiter": {
"enabled": true,
"driver": "redis", // Usually "memory" or "redis"
"api_rate_limit": {
"max_requests": 100,
"window_seconds": 60
},
"websocket_rate_limit": { // Applied on connection
"max_requests": 20,
"window_seconds": 60
}
},
"metrics": {
"enabled": true,
"driver": "prometheus", // Currently only prometheus supported
"port": 9601
},
"ssl": {
"enabled": false,
// "cert_path": "/path/to/fullchain.pem",
// "key_path": "/path/to/privkey.pem"
}
// ... many other options available, see src/options.rs
}
βββββββββββββββββββ βββββββββββββββββββββ βββββββββββββββββββ
β Client Apps β β Load Balancer β β Sockudo Node β
β (Web/Mobile/IoT)βββββΊβ (e.g., Nginx, βββββΊβ (Rust) β
β using PusherJS β β AWS ALB) β β β
βββββββββββββββββββ βββββββββββββββββββββ βββββββββββββββββββ
β β²
β β Pub/Sub
βββββββββββββββββββ β βΌ
β Pub/Sub Backend βββββββββββββββ
β (Redis/NATS for β
β HORIZONTAL_ADAPTER)β
βββββββββββββββββββ
β
βββββββββββββββββββββββ β ββββββββββββββββββββββ
β App Config Storage ββββββββΊβ Cache Storage β
β (MySQL/PG/DynamoDB/ β β (Redis/Memory) β
β Memory) β ββββββββββββββββββββββ
βββββββββββββββββββββββ
- Connection Handler (
adapter::handler
): Manages individual WebSocket connections, message parsing, authentication, and routing to appropriate handlers. - Channel Manager (
channel::manager
): Handles channel subscriptions, presence state, and signature validation for channel access. - Adapter (
adapter
): Abstract interface for connection management and message broadcasting.- Local Adapter (
adapter::local_adapter
): Single-node operation. - Horizontal Adapters (
adapter::redis_adapter
,adapter::redis_cluster_adapter
,adapter::nats_adapter
): Enable multi-node setups by using Redis, Redis Cluster, or NATS for pub/sub to synchronize state and broadcast messages across instances.
- Local Adapter (
- App Manager (
app::manager
): Manages application configurations (keys, secrets, limits, features), with backends like Memory, MySQL, PostgreSQL, or DynamoDB. - Cache Manager (
cache::manager
): Provides caching for frequently accessed data (e.g., channel information, app settings) with Memory or Redis backends. - Queue Manager (
queue::manager
): Handles background tasks, primarily for delivering webhooks, using Memory, Redis, Redis Cluster, or SQS. - Rate Limiter (
rate_limiter
): Protects against abuse with configurable limits, using Memory or Redis. - Webhook System (
webhook
): Delivers real-time server events (e.g., channel occupied/vacated, member added/removed) to configured HTTP endpoints or AWS Lambda functions. - Metrics Collector (
metrics
): Exposes Prometheus metrics for monitoring performance and health. - HTTP Handler (
http_handler
): Provides the Pusher-compatible REST API for triggering events and querying server state. - WebSocket Handler (
ws_handler
): Manages the WebSocket upgrade process and initial connection setup.
- Single Node: Ideal for development, testing, and smaller applications. Uses
local
adapter andmemory
for other components if not otherwise configured. - Multi-Node (Clustered): Achieves horizontal scalability using Redis, Redis Cluster, or NATS as the
adapter.driver
for message broadcasting and state synchronization between Sockudo instances. - Microservices Integration: Sockudo can act as the real-time layer in a microservices architecture, receiving events to broadcast via its HTTP API.
- Serverless Integration: Webhooks can trigger AWS Lambda functions, allowing for serverless processing of Sockudo events.
Sockudo implements the Pusher WebSocket protocol. This means you can use any Pusher-compatible client-side library to connect to Sockudo for real-time messaging.
For triggering events from your application backend (server-to-server), you would use Sockudo's HTTP API (see API Reference section), potentially with an HTTP client or a Pusher server-side library configured to point to Sockudo's HTTP API endpoints.
This is the most common library for frontend integration.
npm install pusher-js
import Pusher from 'pusher-js';
const pusher = new Pusher('your-app-key', { // Use the 'key' from your app config
wsHost: 'localhost', // Your Sockudo host
wsPort: 6001, // Your Sockudo WebSocket port
wssPort: 6001, // Your Sockudo WSS port (if SSL_ENABLED=true)
forceTLS: false, // Set true if using WSS
enabledTransports: ['ws', 'wss'],
cluster: 'mt1', // Required by pusher-js, value doesn't impact Sockudo directly
disableStats: true, // Recommended
authEndpoint: '/your-backend-auth-endpoint-for-private-channels', // For private/presence channels
// authTransport: 'ajax' // or 'jsonp'
});
const channel = pusher.subscribe('public-channel');
channel.bind('some-event', (data) => {
console.log('Received data:', data);
});
Most Pusher client libraries for other languages (Python, Ruby, Java, .NET/C#, Swift, Android, etc.) can be configured to connect to a custom host and port, allowing them to work with Sockudo. You'll typically need to set:
- App Key
- Host (your Sockudo server address)
- Port (your Sockudo WebSocket port)
forceTLS
(or equivalent for secure connections)- An
authEndpoint
if using private or presence channels.
You can also connect directly using any standard WebSocket client, but you'll need to implement the Pusher message format manually (e.g., for pusher:subscribe
, pusher:ping
).
const ws = new WebSocket('ws://localhost:6001/app/your-app-key');
ws.onopen = () => {
ws.send(JSON.stringify({
event: 'pusher:subscribe',
data: { channel: 'my-channel' }
}));
};
// ... handle other WebSocket events
Use Sockudo's HTTP API. Examples using curl
are in the "Usage Examples" and "API Reference" sections.
Libraries like Pusher's official PHP or Go server libraries can also be used if you configure their host/port to point to your Sockudo HTTP API endpoint (http://localhost:6001
by default).
PHP (Pusher Server SDK - configured for Sockudo)
<?php
require __DIR__ . '/vendor/autoload.php';
$options = [
'host' => 'localhost', // Sockudo HTTP API host
'port' => 6001, // Sockudo HTTP API port
'scheme' => 'http', // 'https' if SSL_ENABLED=true on Sockudo
'encrypted' => false, // Relevant for Pusher Cloud, less so here
'useTLS' => false, // Set true if Sockudo is using HTTPS
// cluster parameter is often not needed when directly targeting Sockudo
];
// Ensure 'your-app-id' matches an 'id' field in one of your configured apps in Sockudo
$pusher = new Pusher\Pusher('your-app-key', 'your-app-secret', 'your-app-id', $options);
$data = ['message' => 'hello world from PHP backend via Sockudo'];
$pusher->trigger('my-channel', 'my-event', $data);
?>
Go (Pusher HTTP Go - configured for Sockudo)
package main
import (
"fmt"
"[github.com/pusher/pusher-http-go/v5](https://github.com/pusher/pusher-http-go/v5)"
)
func main() {
client := pusher.Client{
AppID: "your-app-id", // Matches 'id' in Sockudo app config
Key: "your-app-key", // Matches 'key'
Secret: "your-app-secret", // Matches 'secret'
Host: "localhost:6001", // Sockudo HTTP API host and port
Secure: false, // Set to true if Sockudo is using HTTPS
}
data := map[string]string{"message": "hello from Go backend via Sockudo"}
events, err := client.Trigger("my-channel", "my-event", data)
if err != nil {
fmt.Println("Error triggering event:", err)
return
}
fmt.Printf("Events triggered: %+v\n", events)
}
Refer to README-Docker.md
for comprehensive Docker deployment instructions.
# Start development environment (uses docker-compose.yml and docker-compose.dev.yml)
make dev # (If Makefile provides this shortcut)
# OR
docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d
# View logs
docker-compose logs -f sockudo
# Run tests (if configured in a Makefile or script)
# make test
# Production deployment (uses docker-compose.yml and docker-compose.prod.yml)
make prod # (If Makefile provides this shortcut)
# OR
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
# Scale to multiple instances (example)
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d --scale sockudo=3
# Monitor performance (example for Docker stats)
docker stats
A basic Kubernetes deployment might look like this (adapt as needed):
apiVersion: apps/v1
kind: Deployment
metadata:
name: sockudo
spec:
replicas: 3
selector:
matchLabels:
app: sockudo
template:
metadata:
labels:
app: sockudo
spec:
containers:
- name: sockudo
image: sockudo/sockudo:latest
ports:
- name: websocket
containerPort: 6001 # Sockudo's application port
- name: metrics
containerPort: 9601 # Sockudo's metrics port
env:
- name: ADAPTER_DRIVER
value: "redis" # or "nats", "redis-cluster"
- name: REDIS_URL
value: "redis://your-redis-service:6379"
# Add other necessary environment variables or configmap mounts
# - name: CONFIG_FILE_PATH
# value: "/app/config/config.json"
# volumeMounts:
# - name: sockudo-config
# mountPath: /app/config
# volumes:
# - name: sockudo-config
# configMap:
# name: sockudo-configmap
Ensure your Kubernetes setup includes appropriate services, ingress, and configuration for Redis/NATS if using horizontal scaling.
Sockudo can be deployed to various cloud platforms:
- Use the production Docker image.
- Configure Application Load Balancer (ALB) or Network Load Balancer (NLB) with WebSocket support (sticky sessions might be needed depending on adapter and client behavior, though ideally not with a proper horizontal adapter).
- Use ElastiCache for Redis (standalone or cluster) if using Redis adapter/cache/queue.
- Use RDS for MySQL/PostgreSQL if using those for AppManager.
- Use Amazon SQS if using the SQS queue driver.
- Enable WebSocket support in Cloud Run or use GKE.
- Use Cloud Memorystore for Redis.
- Configure Cloud SQL for persistent app storage.
- Use Azure Cache for Redis.
- Configure Azure Database for MySQL/PostgreSQL.
Sockudo exposes Prometheus metrics at the /metrics
endpoint (default port 9601).
Key metrics include:
- Connection Metrics:
sockudo_connected
: Current number of active WebSocket connections.sockudo_new_connections_total
: Total number of new connections established.sockudo_new_disconnections_total
: Total number of disconnections.
- Message Metrics:
sockudo_ws_messages_sent_total
: Total WebSocket messages sent from server to clients.sockudo_ws_messages_received_total
: Total WebSocket messages received by server from clients.sockudo_socket_transmitted_bytes
: Total bytes transmitted over WebSockets.sockudo_socket_received_bytes
: Total bytes received over WebSockets.
- HTTP API Metrics:
sockudo_http_calls_received_total
: Total HTTP API calls received.sockudo_http_transmitted_bytes
: Total bytes sent in HTTP API responses.sockudo_http_received_bytes
: Total bytes received in HTTP API requests.
- Horizontal Adapter Metrics (if using Redis/NATS adapter):
sockudo_horizontal_adapter_resolve_time
: Histogram of request resolution time between nodes.sockudo_horizontal_adapter_resolved_promises
: Total promises fulfilled by other nodes.sockudo_horizontal_adapter_sent_requests
: Total requests sent to other nodes.sockudo_horizontal_adapter_received_requests
: Total requests received from other nodes.sockudo_horizontal_adapter_received_responses
: Total responses received from other nodes.
- Channel Statistics (can be inferred or might require specific metrics not listed but common):
sockudo_channel_count
(conceptual, often derived fromget_channels_with_socket_count
)sockudo_active_subscriptions
(conceptual)
If using the Docker setup with Grafana (docker-compose.prod.yml
), a pre-configured dashboard might be available. Otherwise, you can import/create dashboards in Grafana using Prometheus as a data source.
Access Grafana at http://localhost:3000
(default credentials may vary, check docker-compose.prod.yml
or README-Docker.md
).
- Application Health:
GET /up/{app_id}
- Checks if a specific application is responsive. - Metrics Endpoint:
GET /metrics
(default on port 9601) - Exposes Prometheus metrics. - General Usage/Memory:
GET /usage
- Provides memory usage statistics of the Sockudo server.
Sockudo validates auth
tokens for private and presence channel subscriptions. The signature is typically generated on your backend.
The string to sign depends on the channel type:
- Private channels:
socket_id:channel_name
- Presence channels:
socket_id:channel_name:channel_data_json_string
(wherechannel_data_json_string
is the JSON string provided in thechannel_data
field of the subscription request).
Example Server-Side Signature Generation (Node.js conceptual):
const crypto = require('crypto');
function generateChannelAuth(socketId, channelName, appSecret, channelDataString = null) {
let stringToSign = `${socketId}:${channelName}`;
if (channelDataString) { // For presence channels
stringToSign += `:${channelDataString}`;
}
const signature = crypto.createHmac('sha256', appSecret)
.update(stringToSign)
.digest('hex');
return signature; // The client would send appKey:signature
}
// For a private channel:
// const appKey = "your-app-key";
// const signature = generateChannelAuth(clientSocketId, "private-mychannel", "your-app-secret");
// const authString = `${appKey}:${signature}`;
// For a presence channel:
// const channelData = JSON.stringify({ user_id: "123", user_info: { name: "Alice" } });
// const presenceSignature = generateChannelAuth(clientSocketId, "presence-mychannel", "your-app-secret", channelData);
// const presenceAuthString = `${appKey}:${presenceSignature}`;
Refer to src/channel/manager.rs
(method get_data_to_sign_for_signature
) for the exact server-side string construction.
If enable_user_authentication
is true for an app, clients can send a pusher:signin
event.
The string to sign for user authentication is socket_id::user::user_data_json_string
.
Example Server-Side User Auth Token Generation (Node.js conceptual):
const crypto = require('crypto');
function generateUserAuth(socketId, userDataJsonString, appSecret) {
const stringToSign = `${socketId}::user::${userDataJsonString}`;
const signature = crypto.createHmac('sha256', appSecret)
.update(stringToSign)
.digest('hex');
return signature; // The client would send this as part of the pusher:signin 'auth' field
}
// const appKey = "your-app-key";
// const userData = JSON.stringify({ id: "user456", name: "Bob" });
// const userAuthSignature = generateUserAuth(clientSocketId, userData, "your-app-secret");
// Client sends: { event: "pusher:signin", data: { auth: `${appKey}:${userAuthSignature}`, user_data: userData }}
// Note: The `src/app/auth.rs` `validate_channel_auth` and `sing_in_token_for_user_data` suggest the client sends only the signature part in `auth`, and `user_data` separately. Your client example shows this correctly.
Refer to src/app/auth.rs
(method sing_in_token_for_user_data
) for server-side logic.
Configure rate limits in config/config.json
or via environment variables to protect against abuse. Sockudo supports separate limits for HTTP API requests and WebSocket connections.
Backends like Memory or Redis can be used for rate limiting.
{
"rate_limiter": {
"enabled": true,
"driver": "redis", // "memory" or "redis"
"api_rate_limit": {
"max_requests": 1000, // e.g., per minute for all API calls from an IP
"window_seconds": 60
},
"websocket_rate_limit": { // Applied per IP at connection time
"max_requests": 60, // e.g., max 60 connection attempts per minute from an IP
"window_seconds": 60
},
"redis": { // If Redis driver is used for rate limiting
"prefix": "sockudo_rl:"
// url_override can also be set here if different from main Redis
}
}
}
Client-specific event rate limits are configured per-application (e.g., max_client_events_per_second
).
Enable HTTPS and WSS in production by setting ssl.enabled = true
in config/config.json
or SSL_ENABLED=true
(env var) and providing paths to your SSL certificate and key.
Refer to README-Docker.md
for Docker-specific SSL setup with Nginx or Let's Encrypt.
"ssl": {
"enabled": true,
"cert_path": "/etc/letsencrypt/live/[your-domain.com/fullchain.pem](https://your-domain.com/fullchain.pem)",
"key_path": "/etc/letsencrypt/live/[your-domain.com/privkey.pem](https://your-domain.com/privkey.pem)",
"redirect_http": true, // Redirect HTTP traffic to HTTPS
"http_port": 8080 // Port for HTTP redirect server if Sockudo handles SSL
}
Sockudo aims for compatibility with the Pusher WebSocket protocol. Key events include:
pusher:connection_established
: Sent to the client upon successful connection. Containssocket_id
andactivity_timeout
.pusher:error
: Sent to the client when an error occurs (e.g., auth failure, invalid message). Containscode
andmessage
.pusher:ping
: Sent by the server to check if the client is alive. Client should respond withpusher:pong
.pusher:pong
: Sent by the client in response to apusher:ping
.
pusher:subscribe
: Sent by the client to subscribe to a channel.data
includeschannel
name and optionallyauth
string (for private/presence) andchannel_data
(for presence).pusher:unsubscribe
: Sent by the client to unsubscribe from a channel.data
includeschannel
name.pusher_internal:subscription_succeeded
: Sent to the client by the server confirming successful subscription to a channel. For presence channels,data
includes current member list (presence.hash
,presence.ids
,presence.count
).pusher:cache_miss
: Sent to the client if they subscribe to a cache channel and no data is initially cached.
pusher_internal:member_added
: Sent to clients in a presence channel when a new member joins.data
includesuser_id
anduser_info
.pusher_internal:member_removed
: Sent to clients in a presence channel when a member leaves.data
includesuser_id
.
pusher:signin
: Sent by the client to authenticate themselves as a user.data
includesauth
(app_key:signature) anduser_data
(JSON string with user details likeid
).pusher:signin_success
: Sent to the client by the server upon successful user sign-in.
client-*
(e.g.,client-message
): Custom events sent by one client, broadcast by the server to other clients on the same private or presence channel (excluding the sender by default, ifsocket_id
is provided when triggering via HTTP API). The app must haveenable_client_messages: true
.
All endpoints are prefixed with /apps/{app_id}/
. Authentication is required for these endpoints.
- Endpoint:
POST /apps/{app_id}/events
- Description: Triggers an event on one or more channels.
- Body:
{ "name": "your-event-name", "channels": ["channel-1", "channel-2"], // Or "channel": "single-channel" "data": {"key": "value"}, // JSON string or object for event data "socket_id": "optional_socket_id_to_exclude" // Optional // "info": "user_count,subscription_count" // Optional: to request info about channels in response }
- Response:
200 OK
with{"ok":true}
or channel info if requested.
- Endpoint:
POST /apps/{app_id}/batch_events
- Description: Triggers multiple events in a single request.
- Body:
{ "batch": [ {"name": "event1", "channel": "channel1", "data": "data1"}, {"name": "event2", "channels": ["channel2", "channel3"], "data": {"complex":"data2"}} // Each item can also have "socket_id" and "info" ] }
- Response:
200 OK
with{}
or{"batch": [...]}
containing info for each event if requested.
- Endpoint:
GET /apps/{app_id}/channels/{channel_name}
- Description: Retrieves information about a specific channel.
- Query Parameters:
info
: Comma-separated list of attributes to retrieve (e.g.,user_count
,subscription_count
,cache
).
- Response:
200 OK
with JSON containing channel status (e.g.,{"occupied": true, "user_count": 5, "subscription_count": 10}
).
- Endpoint:
GET /apps/{app_id}/channels
- Description: Retrieves a list of active channels in an application.
- Query Parameters:
filter_by_prefix
: Filters channels by the given prefix.info
: Comma-separated list of attributes to retrieve for each channel (e.g.,user_count
).
- Response:
200 OK
with JSON{"channels": {"channel_name": {"user_count": 2}, ...}}
.
- Endpoint:
GET /apps/{app_id}/channels/{channel_name}/users
- Description: Retrieves the list of users subscribed to a specific presence channel.
- Response:
200 OK
with JSON{"users": [{"id": "user_id_1"}, {"id": "user_id_2"}]}
.
- Endpoint:
POST /apps/{app_id}/users/{user_id}/terminate_connections
- Description: Forcibly disconnects all WebSocket connections associated with a given
user_id
. - Response:
200 OK
with{"ok":true}
.
- Health Check:
GET /up/{app_id}
- Returns200 OK
if the app is known (even if no connections). - Server Usage:
GET /usage
- Returns server memory statistics. - Prometheus Metrics:
GET /metrics
(default port 9601) - Exposes metrics in Prometheus format.
Sockudo includes a suite of tests to ensure reliability.
# Run all unit and integration tests (ensure dependencies like Redis might be needed for some)
cargo test
# For Docker-based testing environment, refer to README-Docker.md
# Example: make ci-test (if Makefile provides this)
The project includes an interactive tester in test/interactive/
which can be used to manually test WebSocket connections, subscriptions, client events, and webhooks against a running Sockudo instance.
Automated tests can be found in test/automated/
which might include scripts for load testing or specific feature validation.
We welcome contributions! Please see our CONTRIBUTING.md for guidelines on how to get started, coding standards, and the PR review process.
For more in-depth information:
- Explore the
src/
directory for detailed module implementations. - Configuration options are detailed in
src/options.rs
. - Docker setup is described in
README-Docker.md
. - (If a dedicated documentation site exists, link it here, e.g.,
https://sockudo.app
as mentioned in the original README).
- β Core WebSocket functionality
- β Channel system (public, private, presence)
- β Authentication and rate limiting
- β Docker deployment
- β Prometheus metrics
- β Support for Redis, NATS, Redis Cluster adapters
- β Support for Memory, MySQL, PgSQL, DynamoDB AppManagers
(Update this section with your actual roadmap and completed features)
This project is licensed under the AGPL-3.0 License - see the LICENSE file for details. (Updated based on the badge at the top of your README).
- Built with Tokio for async runtime.
- Uses fastwebsockets for WebSocket implementation.
- Inspired by Pusher and similar real-time messaging platforms like Laravel Reverb.
- Thanks to the Rust community for its robust ecosystem and excellent crates.
- GitHub Issues: Report bugs and request features
- Crates.io: View package details
- Discord: Join the RustNSparks server
- X: Follow us on X
β Star us on GitHub if Sockudo helps your project! β