Architecture¶
This page describes the internal design of SocketSpec: how components are organized, how a message flows from client to handler, and where extension points exist.
Design Philosophy¶
SocketSpec is built on three principles:
- Convention over configuration. Sensible defaults for authentication, rate limiting, session management, and error handling. Override only what you need.
- Framework independence. The core library has no dependency on FastAPI, Starlette, or any web framework. Adapters bridge the gap through a minimal
RawSocketprotocol. - Observable by default. Every registered event automatically generates documentation metadata. The interactive UI is not an afterthought -- it is a first-class output of the event registration system.
Component Overview¶
graph TB
Client["Client (Browser / SDK)"]
Adapter["Framework Adapter (FastAPI)"]
App["SocketApp"]
Auth["AuthBackend"]
Origins["OriginValidator"]
Manager["ConnectionManager"]
Session["SessionManager"]
MW["Middleware Chain"]
Router["EventRouter"]
DI["DependencyResolver"]
Rooms["RoomManager"]
Backend["Backend (Memory / Redis)"]
Registry["EventRegistry"]
DocsEngine["DocsEngine"]
DocsUI["/socket-docs UI"]
Client -->|WebSocket| Adapter
Adapter --> App
App --> Origins
App --> Auth
App --> Manager
App --> Session
App --> MW
MW --> Router
Router --> DI
Router -->|handler| Rooms
Manager --> Backend
Rooms --> Backend
Registry --> DocsEngine
DocsEngine --> DocsUI
Core Components¶
SocketApp¶
The central orchestrator. SocketApp owns the event registry, connection manager, room manager, session manager, and middleware chain. It exposes the decorator API (@socket.on, @socket.on_connect, @socket.middleware) and the three entry points that adapters call: handle_connect, handle_event, and handle_disconnect.
EventRegistry¶
Stores all registered event handlers along with their metadata: event name, description, tags, Pydantic payload model, emits/broadcasts declarations, and deprecation status. The registry is frozen at startup after _startup_validate() confirms there are no duplicate event names or reserved-name collisions.
EventRouter¶
Dispatches inbound messages to their registered handler. The router resolves dependencies via DependencyResolver, runs the middleware chain, validates the payload against the Pydantic model, and catches handler exceptions to emit structured HANDLER_ERROR envelopes.
ConnectionManager¶
Tracks all active connections. Each connection is identified by a UUID and stored in the backend. The manager provides connect(), disconnect(), get(), and broadcast() operations.
RoomManager¶
Manages named rooms with join, leave, broadcast, and membership queries. Supports pattern-based guards ("admin:{section}") that extract path variables and pass them to guard functions. Room joins are atomic: if a lifecycle hook raises an exception, the join is rolled back.
SessionManager¶
Monitors connection health through heartbeat ping/pong, idle timeout detection, max session duration enforcement, and JWT token refresh warnings. Each connection gets an independent session task.
DependencyResolver¶
Resolves Depends() parameters in handler signatures using an AsyncExitStack for cleanup. Supports nested dependencies and generator-based teardown.
Backend (Memory / Redis)¶
The storage layer for connections and pub/sub. The default MemoryBackend stores everything in-process. A Redis backend can be plugged in for multi-process deployments.
Request Lifecycle¶
When a client sends a WebSocket message, it passes through the following stages:
1. Adapter receives raw WebSocket frame
|
2. JSON parse
|-- Failure --> VALIDATION_ERROR sent to client
|
3. Payload size check
|-- Exceeds limit --> PAYLOAD_TOO_LARGE sent to client
|
4. Rate limit check
|-- Bucket exhausted --> RATE_LIMIT_ERROR sent to client
|
5. Session activity update (touch last_active timestamp)
|
6. System event check (__pong__ handled internally, not routed)
|
7. Middleware chain (FIFO order)
|
8. Pydantic payload validation against registered model
|-- Failure --> VALIDATION_ERROR sent to client
|
9. Dependency injection resolution
|
10. Handler execution
|-- Exception --> HANDLER_ERROR sent to client (connection survives)
At every failure point, the client receives a structured error envelope. The connection is never terminated due to a handler or validation error.
Connection Lifecycle¶
1. WebSocket handshake (handled by framework adapter)
|
2. Origin validation
|-- Rejected --> Connection closed with code 4003
|
3. Authentication (if AuthBackend configured)
|-- Failed --> AUTH_ERROR sent, connection closed with code 4001
|
4. Connection registered in ConnectionManager
|
5. Session task started (heartbeat, idle timeout, max duration)
|
6. on_connect hooks fire
|
7. Message receive loop (steps from Request Lifecycle above)
|
8. Connection closes (client disconnect, timeout, or server shutdown)
|
9. on_disconnect hooks fire
|
10. Connection removed from all rooms
|
11. Connection deregistered from ConnectionManager
|
12. Session task cancelled
Extension Points¶
| Extension | Mechanism | Documentation |
|---|---|---|
| Custom authentication | Implement AuthBackend protocol |
Security Guide |
| Custom middleware | @socket.middleware decorator |
Middleware Guide |
| Custom dependencies | Depends() in handler signature |
Dependency Injection Guide |
| Custom storage backend | Implement BackendAdapter protocol |
Writing a New Adapter |
| Room access control | @socket.room_guard("pattern") |
Rooms Tutorial |
| Lifecycle hooks | @socket.on_connect, @socket.on_disconnect, @socket.on_error |
SocketApp Reference |
Wire Protocol¶
SocketSpec uses a simple JSON envelope for all messages. See the Wire Protocol page for the complete specification.