A high-performance real-time pub/sub messaging server written in Rust. Clients connect over WebSocket, authenticate with JWT, subscribe to channels, and receive messages pushed via an HTTP API.
- WebSocket pub/sub — Clients connect to
/wsand use a simple JSON protocol to subscribe/unsubscribe to channels. - JWT authentication — HS256 tokens with per-client channel allowlists.
- HTTP publish API —
POST /api/publishwith API key authentication. - NATS clustering — Optional NATS broker for multi-node message forwarding.
- Lock-free registry —
DashMap-backed session and channel storage for high concurrency. - Zero-copy fanout — Messages are serialized once and cloned as
Bytesacross subscribers.
- Rust 1.85+ (uses
edition = "2024") - (Optional) NATS server for multi-node mode
# Build
cargo build
# Run the server (binds to 0.0.0.0:8080)
cargo run -p turbine-server
# Run tests
cargo testThe server starts in single-node mode if NATS is not available.
| Variable | Default | Description |
|---|---|---|
NATS_URL |
nats://127.0.0.1:4222 |
NATS server connection URL |
NATS_PREFIX |
turbine |
NATS subject prefix for channels |
Note: JWT secret and publish API key are currently hardcoded as
devsecretanddevkeyrespectively. Replace these for production use.
┌─────────────┐ ┌─────────────────────────────────────┐
│ WS Client │◄──ws──►│ turbine-server │
└─────────────┘ │ ┌─────────┐ ┌──────────────────┐ │
│ │ handler │ │ HTTP (publish) │ │
┌─────────────┐ │ └────┬────┘ └────────┬─────────┘ │
│ Publisher │──POST──►│ │ │ │
└─────────────┘ │ ┌───▼─────────────────▼──────────┐ │
│ │ turbine-core │ │
│ │ Engine → Registry → Fanout │ │
│ └──────────────┬─────────────────┘ │
│ │ │
│ ┌──────▼──────┐ │
│ │ NATS Broker │ (optional) │
│ └─────────────┘ │
└─────────────────────────────────────┘
| Crate | Description |
|---|---|
turbine-core |
Engine, registry, auth, protocol. Pure logic, no I/O. |
turbine-server |
Axum-based HTTP + WebSocket server. Thin adapter. |
Connect to ws://host:8080/ws and exchange JSON messages.
All commands follow this format:
{ "id": 1, "method": "<method>", "params": { ... } }{ "id": 1, "method": "connect", "params": { "token": "<jwt>" } }{ "id": 2, "method": "subscribe", "params": { "channel": "chat:lobby" } }{ "id": 3, "method": "unsubscribe", "params": { "channel": "chat:lobby" } }{ "id": 4, "method": "ping" }Success response:
{ "id": 1, "result": { "connected": true } }Error response:
{ "id": 2, "error": { "code": 403, "message": "forbidden" } }Push message (no request ID):
{ "method": "publication", "params": { "channel": "chat:lobby", "data": { "text": "hello" } } }| Code | Meaning |
|---|---|
| 400 | Bad command / bad UTF-8 |
| 401 | Not authenticated |
| 403 | Channel not allowed |
| 429 | Too many subscriptions |
| 500 | Internal error |
GET /healthz
Returns 200 OK with body ok.
POST /api/publish
x-api-key: <api-key>
Content-Type: application/json
{
"channel": "chat:lobby",
"data": { "text": "hello world" }
}
Response:
{ "ok": true, "delivered": 42 }delivered is the number of local sessions that received the message.
Tokens are signed with HS256. The payload must include:
{
"sub": "user-123",
"exp": 1700000000,
"channels": ["chat:lobby", "notifications:user-123"]
}| Field | Type | Description |
|---|---|---|
sub |
string |
Client identifier |
exp |
number |
Expiration (Unix timestamp, seconds) |
channels |
string[] |
Allowed channels. Empty array = unrestricted access. |
| Setting | Default | Description |
|---|---|---|
| Max subs per session | 64 | Maximum channel subscriptions |
| Outbound buffer size | 256 | MPSC channel capacity per session |
Messages are silently dropped if a client's outbound buffer is full (try_send).
This project is currently unlicensed. Add a LICENSE file to specify terms.