Refactor the stop case in Hub.Run() to iterate h.rooms directly
instead of h.connRoom. For each room, iterate all connections and
send CloseGoingAway frame before closing. After the loop, reset both
maps (h.rooms, h.connRoom) in one shot rather than deleting entries
incrementally. This is cleaner and avoids modifying a map during
iteration.
Add TestIntegration_GracefulShutdownMultiRoom to verify clients in
separate rooms all receive close frames during shutdown.
🤖 Assisted by the code-assist SOP
WebSocket Relay Server
A minimal Go WebSocket relay server that broadcasts every incoming message to all connected clients. Supports TLS, Prometheus metrics, configurable logging, and graceful shutdown.
Features
- Fan-out broadcasting — every message is relayed to all connected clients
- TLS support — optional
wss://via cert/key PEM files - Prometheus metrics — connection counts, message totals, disconnections
- Configurable logging — output to stdout, stderr, or file with level filtering
- Graceful shutdown — clean exit on SIGINT/SIGTERM with client notification
- Zero dependencies at runtime — single static binary
Quick Start
# Install dependencies
go mod tidy
# Run the server (defaults to ws://localhost:8443)
make run
# Or with a custom config
go run . --config-file=./config.yaml
Open example/index.html in multiple browser tabs to test the P2P chat demo.
Configuration
Edit config.yaml:
server:
port: 8443
tls:
enabled: false # Set true for wss://
cert_file: cert.pem
key_file: key.pem
metrics:
enabled: true
port: 9090 # Prometheus metrics at :9090/metrics
logging:
output: stderr # stdout, stderr, or a file path
level: info # debug, info, warn, error
Override the config file path with --config-file:
./websocket-relay --config-file=/etc/relay/config.yaml
Logging
The logging section controls where and what the server logs:
| Field | Values | Default | Description |
|---|---|---|---|
output |
stdout, stderr, or a file path |
stderr |
Log output destination |
level |
debug, info, warn, error |
info |
Minimum log level to output |
Examples:
# Log everything to a file
logging:
output: /var/log/websocket-relay.log
level: debug
# Quiet mode — only warnings and errors to stderr
logging:
output: stderr
level: warn
Log messages are prefixed with the level: [DEBUG], [INFO], [WARN], [ERROR].
File output uses append mode (O_APPEND) so logs are preserved across restarts and safe for external log rotation tools.
Usage
Connect any WebSocket client to the server:
const ws = new WebSocket('ws://localhost:8443/');
ws.onmessage = (event) => console.log('Received:', event.data);
ws.onopen = () => ws.send('Hello from client!');
With TLS enabled:
const ws = new WebSocket('wss://localhost:8443/');
All messages sent by any client are broadcast to every connected client (including the sender).
Build
make build # Build binary → build/websocket-relay
make release # Cross-compile linux/amd64 + darwin/arm64
make clean # Remove build artifacts
Testing
make test # Run all tests (unit + integration)
Metrics
When metrics.enabled is true, Prometheus metrics are exposed at http://localhost:9090/metrics:
| Metric | Type | Description |
|---|---|---|
websocket_connected_clients |
Gauge | Currently connected clients |
websocket_messages_total |
Counter | Total messages relayed |
websocket_connections_total |
Counter | Total connections established |
websocket_disconnections_total |
Counter | Total disconnections |
Graceful Shutdown
The server handles SIGINT and SIGTERM signals:
- Stops accepting new connections
- Sends WebSocket
CloseGoingAwayframe to all connected clients - Closes all connections and exits cleanly
Shutdown timeout is 10 seconds.
Project Structure
websocket-relay/
├── main.go # Entry point, signal handling, graceful shutdown
├── internal/
│ ├── config/config.go # YAML config loader
│ ├── hub/hub.go # WebSocket hub, connection management, broadcast
│ ├── logging/logging.go # Log output setup and leveled logger
│ └── metrics/metrics.go # Prometheus metric definitions
├── example/index.html # Browser P2P chat demo
├── config.yaml # Runtime configuration
├── config.example.yaml # Example config with TLS and logging
└── Makefile # Build, test, release commands
License
See repository for license details.