go-ws¶
go-ws is a WebSocket hub library for Go. It implements centralised connection management using the hub pattern, named channel pub/sub, optional token-based authentication at upgrade time, a client-side reconnecting wrapper with exponential backoff, and a Redis pub/sub bridge that coordinates broadcasts across multiple hub instances.
| Module | forge.lthn.ai/core/go-ws |
| Go version | 1.26+ |
| Licence | EUPL-1.2 |
| Repository | ssh://git@forge.lthn.ai:2223/core/go-ws.git |
Quick Start¶
package main
import (
"context"
"net/http"
"forge.lthn.ai/core/go-ws"
)
func main() {
hub := ws.NewHub()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go hub.Run(ctx)
http.HandleFunc("/ws", hub.Handler())
http.ListenAndServe(":8080", nil)
}
Once running, clients connect via WebSocket and can subscribe to named channels. The server pushes messages to all connected clients or to subscribers of a specific channel.
Sending Messages¶
// Broadcast to every connected client.
hub.SendEvent("deploy:started", map[string]any{"env": "production"})
// Send process output to subscribers of "process:build-42".
hub.SendProcessOutput("build-42", "Compiling main.go...")
// Send a process status change.
hub.SendProcessStatus("build-42", "exited", 0)
Adding Authentication¶
auth := ws.NewAPIKeyAuth(map[string]string{
"secret-key-1": "user-alice",
"secret-key-2": "user-bob",
})
hub := ws.NewHubWithConfig(ws.HubConfig{
Authenticator: auth,
OnAuthFailure: func(r *http.Request, result ws.AuthResult) {
log.Printf("rejected connection from %s: %v", r.RemoteAddr, result.Error)
},
})
go hub.Run(ctx)
Clients connect with Authorization: Bearer <key>. Without a valid key, the upgrade is rejected with HTTP 401. When no Authenticator is set, all connections are accepted.
Redis Bridge for Multi-Instance Deployments¶
bridge, err := ws.NewRedisBridge(hub, ws.RedisConfig{
Addr: "localhost:6379",
Prefix: "ws",
})
if err != nil {
log.Fatal(err)
}
if err := bridge.Start(ctx); err != nil {
log.Fatal(err)
}
defer bridge.Stop()
// Messages published via the bridge reach clients on all instances.
bridge.PublishBroadcast(ws.Message{Type: ws.TypeEvent, Data: "hello from instance A"})
bridge.PublishToChannel("process:build-42", ws.Message{
Type: ws.TypeProcessOutput,
Data: "output line",
})
Package Layout¶
The entire library lives in a single Go package (ws). There are no sub-packages.
| File | Purpose |
|---|---|
ws.go |
Hub, Client, Message, ReconnectingClient, connection pumps, channel subscription |
auth.go |
Authenticator interface, AuthResult, APIKeyAuthenticator, BearerTokenAuth, QueryTokenAuth |
errors.go |
Sentinel authentication errors |
redis.go |
RedisBridge, RedisConfig, envelope pattern for loop prevention |
ws_test.go |
Hub lifecycle, broadcast, channel, subscription, and integration tests |
auth_test.go |
Authentication unit and integration tests |
redis_test.go |
Redis bridge integration tests (skipped when Redis is unavailable) |
ws_bench_test.go |
9 benchmarks covering broadcast, channel send, subscribe/unsubscribe, fanout, and end-to-end WebSocket round-trips |
Dependencies¶
| Module | Version | Role |
|---|---|---|
github.com/gorilla/websocket |
v1.5.3 | WebSocket server and client implementation |
github.com/redis/go-redis/v9 |
v9.18.0 | Redis pub/sub bridge (runtime opt-in) |
github.com/stretchr/testify |
v1.11.1 | Test assertions (test-only) |
The Redis dependency is a compile-time import but a runtime opt-in. Applications that never create a RedisBridge incur no Redis connections. There are no CGO requirements; the module builds cleanly on Linux, macOS, and Windows.
Further Reading¶
- Architecture -- Hub pattern, channels, authentication, Redis bridge, concurrency model
- Development Guide -- Building, testing, coding standards, contribution workflow