- Fix metrics: change MessagesTotal, ConnectionsTotal, DisconnectionsTotal from Gauge to Counter with proper _total naming convention - Fix broadcast write-error handling: failed clients now get properly removed with accurate metrics updates - Add graceful shutdown: SIGINT/SIGTERM handling with 10s timeout, CloseGoingAway frame sent to clients before disconnect - Add integration tests: 11 tests using real WebSocket connections covering connect, broadcast, disconnect, concurrency, and shutdown - Fix example client port: changed from 8000 to 8443 to match config - Rewrite README.md to reflect current features and usage - Add AGENTS.md and .agents/summary/ documentation for AI assistants
3.5 KiB
3.5 KiB
Architecture
System Architecture Overview
graph TB
subgraph Clients
C1[WebSocket Client 1]
C2[WebSocket Client 2]
C3[WebSocket Client N]
end
subgraph "WebSocket Relay Server"
EP[HTTP/TLS Endpoint]
HUB[Hub - Connection Manager]
BC[Broadcast Channel]
MET[Prometheus Metrics]
end
subgraph Monitoring
PROM[Prometheus Scraper]
end
C1 -->|ws/wss| EP
C2 -->|ws/wss| EP
C3 -->|ws/wss| EP
EP --> HUB
HUB --> BC
BC -->|relay to all| C1
BC -->|relay to all| C2
BC -->|relay to all| C3
HUB --> MET
MET -->|:9090/metrics| PROM
Design Pattern: Hub-and-Spoke
The application uses a Hub-and-Spoke (fan-out) pattern where:
- Hub is the central coordinator managing all WebSocket connections
- Spokes are individual WebSocket client connections
- Every message received from any client is broadcast to all connected clients
graph LR
subgraph Hub
REG[Register Channel]
UNREG[Unregister Channel]
BCAST[Broadcast Channel]
CLIENTS[Client Map]
end
CONN[New Connection] --> REG
REG --> CLIENTS
DISC[Disconnection] --> UNREG
UNREG --> CLIENTS
MSG[Incoming Message] --> BCAST
BCAST --> CLIENTS
Concurrency Model
The server uses Go's CSP (Communicating Sequential Processes) concurrency model:
sequenceDiagram
participant Client
participant Handler as HTTP Handler
participant Hub as Hub.Run() goroutine
participant Reader as ReadMessage goroutine
Client->>Handler: HTTP Upgrade Request
Handler->>Hub: register <- conn
Hub->>Hub: Add to clients map
Handler->>Reader: Start goroutine
loop Read Messages
Client->>Reader: WebSocket Frame
Reader->>Hub: broadcast <- message
Hub->>Hub: Iterate clients map
Hub->>Client: WriteMessage (fan-out)
end
Reader->>Hub: unregister <- conn (on error/close)
Hub->>Hub: Remove from clients map
Goroutine Lifecycle
| Goroutine | Purpose | Lifetime |
|---|---|---|
main |
HTTP server, accepts connections | Application lifetime |
Hub.Run() |
Processes register/unregister/broadcast channels | Application lifetime |
| Per-client reader | Reads messages from a single client | Client connection lifetime |
| Metrics server | Serves /metrics endpoint |
Application lifetime (if enabled) |
Configuration Architecture
graph LR
CLI[CLI Flag: --config-file] --> LOAD[config.Load]
LOAD --> YAML[YAML Parser]
YAML --> CFG[Config Struct]
CFG --> SRV[Server Setup]
CFG --> TLS[TLS Config]
CFG --> MET[Metrics Setup]
Security Model
- TLS Support: Optional TLS via cert/key PEM files
- Origin Check:
CheckOriginallows all origins (permissive for relay use case) - No Authentication: The relay is designed as a transparent message forwarder
- No Authorization: All connected clients can send/receive all messages
Deployment Architecture
graph TB
subgraph "Build Pipeline"
SRC[Source Code] --> CI[Gitea CI]
CI --> TEST[go test]
CI --> LINT[golangci-lint]
TAG[Git Tag v*] --> REL[Release Pipeline]
REL --> BIN_L[Linux amd64 Binary]
REL --> BIN_M[macOS arm64 Binary]
end
subgraph "Runtime"
BIN[Binary] --> CFG[config.yaml]
CFG --> SERVER[WebSocket Server :8443]
CFG --> METRICS[Metrics Server :9090]
end