Documentation
¶
Overview ¶
Package wshub provides production-ready WebSocket connection management with support for rooms, broadcasting, middleware, hooks, and extensibility.
The central type is Hub, which manages all connected clients and provides broadcasting, room management, and lifecycle hooks. Create one with NewHub and configure it using functional options:
hub := wshub.NewHub(
wshub.WithConfig(wshub.DefaultConfig().WithCompression(true)),
wshub.WithLogger(myLogger),
wshub.WithLimits(wshub.DefaultLimits().WithMaxConnections(10000)),
wshub.WithHooks(wshub.Hooks{
AfterConnect: func(c *wshub.Client) { /* ... */ },
}),
wshub.WithMessageHandler(handler),
)
go hub.Run()
Connection Lifecycle ¶
Upgrade HTTP connections with Hub.UpgradeConnection or the convenience Hub.HandleHTTP handler. Each connection spawns a read pump and write pump goroutine managed by the hub. Use Hub.Shutdown for graceful teardown.
Graceful Draining ¶
For zero-downtime rolling deploys (e.g. Kubernetes), call Hub.Drain before Hub.Shutdown. Drain stops accepting new connections (returning HTTP 503) while letting existing connections finish their in-flight messages. Idle connections are proactively closed after the configured drain timeout (see WithDrainTimeout). Inspect the hub's lifecycle state with Hub.State, Hub.IsRunning, and Hub.IsDraining to implement health and readiness probes:
// preStop / SIGTERM handler ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() hub.Drain(ctx) // stop new connections, wait for existing ones hub.Shutdown(ctx) // force-close anything remaining
Health Probes ¶
Use Hub.HealthHandler and Hub.ReadyHandler as HTTP handlers for liveness and readiness probes. For programmatic access, Hub.Health returns a HealthStatus snapshot, and Hub.Alive / Hub.Ready provide simple boolean checks:
http.Handle("/healthz", hub.HealthHandler())
http.Handle("/readyz", hub.ReadyHandler())
Rooms ¶
Clients can join and leave named rooms via Hub.JoinRoom and Hub.LeaveRoom. Broadcast to a room with Hub.BroadcastToRoom. Rooms are created lazily and removed automatically when the last client leaves.
Middleware ¶
Use MiddlewareChain to compose message-processing pipelines. Built-in middlewares include LoggingMiddleware, RecoveryMiddleware, and MetricsMiddleware. Call MiddlewareChain.Build to pre-compute the chain for better performance.
Message Routing ¶
Router dispatches messages to per-event handlers based on an extractor function, decoupling routing from any specific message format.
Multi-Node Scaling ¶
Scale horizontally by setting an Adapter via WithAdapter. All broadcast and targeted send methods automatically relay messages to other nodes through the adapter's message bus. The core package ships no adapter implementations — import a subpackage to avoid pulling in unwanted dependencies:
- github.com/KARTIKrocks/wshub/adapter/redis — Redis Pub/Sub
- github.com/KARTIKrocks/wshub/adapter/nats — NATS core Pub/Sub
Implement the Adapter interface to integrate any other message bus.
Enable WithPresence to exchange periodic heartbeats between nodes. Hub.GlobalClientCount and Hub.GlobalRoomCount then return cluster-wide totals. Nodes that miss three consecutive heartbeats are automatically evicted from the totals.
Backpressure ¶
When a client's send buffer is full, the hub applies the configured DropPolicy (set via WithDropPolicy):
- DropNewest (default) discards the new message.
- DropOldest evicts the oldest queued message to make room.
In both cases the Hooks.OnSendDropped callback fires so the application can log, disconnect slow clients, or take other corrective action.
Write Coalescing ¶
When Config.CoalesceWrites is true, the write pump batches queued text messages into a single WebSocket frame separated by newline bytes (\n). This reduces the number of syscalls under high throughput. Binary messages are always sent as individual frames. Receivers must split coalesced frames on \n to recover individual messages.
cfg := wshub.DefaultConfig().WithCoalesceWrites(true) hub := wshub.NewHub(wshub.WithConfig(cfg))
Index ¶
- Constants
- Variables
- func AllowAllOrigins(r *http.Request) bool
- func AllowOrigins(origins ...string) func(r *http.Request) bool
- func AllowSameOrigin(r *http.Request) bool
- type Adapter
- type AdapterMessage
- type Client
- func (c *Client) Close() error
- func (c *Client) CloseWithCode(code int, reason string) error
- func (c *Client) ClosedAt() time.Time
- func (c *Client) ConnectedAt() time.Time
- func (c *Client) DeleteMetadata(key string)
- func (c *Client) GetMetadata(key string) (any, bool)
- func (c *Client) GetUserID() string
- func (c *Client) InRoom(room string) bool
- func (c *Client) IsClosed() bool
- func (c *Client) OnClose(fn func(*Client))
- func (c *Client) OnError(fn func(*Client, error))
- func (c *Client) OnMessage(fn func(*Client, *Message))
- func (c *Client) Request() *http.Request
- func (c *Client) RoomCount() int
- func (c *Client) Rooms() []string
- func (c *Client) Send(data []byte) error
- func (c *Client) SendBinary(data []byte) error
- func (c *Client) SendJSON(v any) error
- func (c *Client) SendMessage(msgType MessageType, data []byte) error
- func (c *Client) SendMessageWithContext(ctx context.Context, msgType MessageType, data []byte) (err error)
- func (c *Client) SendRawJSON(data []byte) error
- func (c *Client) SendText(text string) error
- func (c *Client) SendWithContext(ctx context.Context, data []byte) error
- func (c *Client) SetMetadata(key string, value any)
- func (c *Client) SetUserID(userID string) error
- type Config
- func (c Config) WithBufferSizes(read, write int) Config
- func (c Config) WithCheckOrigin(fn func(r *http.Request) bool) Config
- func (c Config) WithCoalesceWrites(enabled bool) Config
- func (c Config) WithCompression(enabled bool) Config
- func (c Config) WithMaxMessageSize(size int64) Config
- func (c Config) WithSendChannelSize(size int) Config
- func (c Config) WithSubprotocols(protocols ...string) Config
- func (c Config) WithTimeouts(writeWait, pongWait, pingPeriod time.Duration) Config
- type DebugMetrics
- func (d *DebugMetrics) DecrementConnections()
- func (d *DebugMetrics) DecrementRooms()
- func (d *DebugMetrics) IncrementConnections()
- func (d *DebugMetrics) IncrementErrors(errorType string)
- func (d *DebugMetrics) IncrementMessagesDropped()
- func (d *DebugMetrics) IncrementMessagesReceived()
- func (d *DebugMetrics) IncrementMessagesSent(count int)
- func (d *DebugMetrics) IncrementRoomJoins()
- func (d *DebugMetrics) IncrementRoomLeaves()
- func (d *DebugMetrics) IncrementRooms()
- func (d *DebugMetrics) RecordBroadcastDuration(duration time.Duration)
- func (d *DebugMetrics) RecordLatency(duration time.Duration)
- func (d *DebugMetrics) RecordMessageSize(size int)
- func (d *DebugMetrics) Reset()
- func (d *DebugMetrics) Stats() DebugStats
- func (d *DebugMetrics) String() string
- type DebugStats
- type DropPolicy
- type HandlerFunc
- type HealthStatus
- type Hooks
- type Hub
- func (h *Hub) Alive() bool
- func (h *Hub) Broadcast(data []byte)
- func (h *Hub) BroadcastBinary(data []byte)
- func (h *Hub) BroadcastBinaryExcept(data []byte, except ...*Client)
- func (h *Hub) BroadcastBinaryToRoom(roomName string, data []byte) error
- func (h *Hub) BroadcastBinaryToRoomExcept(roomName string, data []byte, except ...*Client) error
- func (h *Hub) BroadcastExcept(data []byte, except ...*Client)
- func (h *Hub) BroadcastJSON(v any) error
- func (h *Hub) BroadcastRawJSON(data []byte)
- func (h *Hub) BroadcastText(text string)
- func (h *Hub) BroadcastToRoom(roomName string, data []byte) error
- func (h *Hub) BroadcastToRoomExcept(roomName string, data []byte, except ...*Client) error
- func (h *Hub) BroadcastToRoomWithContext(ctx context.Context, roomName string, data []byte) error
- func (h *Hub) BroadcastWithContext(ctx context.Context, data []byte) error
- func (h *Hub) ClientCount() int
- func (h *Hub) Clients() []*Client
- func (h *Hub) Drain(ctx context.Context) error
- func (h *Hub) GetClient(id string) (*Client, bool)
- func (h *Hub) GetClientByUserID(userID string) (*Client, bool)
- func (h *Hub) GetClientsByUserID(userID string) []*Client
- func (h *Hub) GlobalClientCount() int
- func (h *Hub) GlobalRoomCount(roomName string) int
- func (h *Hub) HandleHTTP() http.HandlerFunc
- func (h *Hub) Health() HealthStatus
- func (h *Hub) HealthHandler() http.HandlerFunc
- func (h *Hub) IsDraining() bool
- func (h *Hub) IsRunning() bool
- func (h *Hub) JoinRoom(client *Client, roomName string) error
- func (h *Hub) LeaveAllRooms(client *Client)
- func (h *Hub) LeaveRoom(client *Client, roomName string) error
- func (h *Hub) NodeID() string
- func (h *Hub) Ready() bool
- func (h *Hub) ReadyHandler() http.HandlerFunc
- func (h *Hub) RoomClients(roomName string) []*Client
- func (h *Hub) RoomCount(roomName string) int
- func (h *Hub) RoomExists(roomName string) bool
- func (h *Hub) RoomNames() []string
- func (h *Hub) Run()
- func (h *Hub) SendBinaryToClient(clientID string, data []byte) error
- func (h *Hub) SendBinaryToUser(userID string, data []byte)
- func (h *Hub) SendToClient(clientID string, data []byte) error
- func (h *Hub) SendToClientWithContext(ctx context.Context, clientID string, data []byte) error
- func (h *Hub) SendToUser(userID string, data []byte)
- func (h *Hub) SendToUserWithContext(ctx context.Context, userID string, data []byte) error
- func (h *Hub) Shutdown(ctx context.Context) error
- func (h *Hub) State() HubState
- func (h *Hub) UpgradeConnection(w http.ResponseWriter, r *http.Request, opts ...UpgradeOption) (*Client, error)
- func (h *Hub) Uptime() time.Duration
- type HubState
- type Limits
- type Logger
- type Message
- type MessageType
- type MetricsCollector
- type Middleware
- type MiddlewareChain
- type NoOpLogger
- type NoOpMetrics
- func (n *NoOpMetrics) DecrementConnections()
- func (n *NoOpMetrics) DecrementRooms()
- func (n *NoOpMetrics) IncrementConnections()
- func (n *NoOpMetrics) IncrementErrors(errorType string)
- func (n *NoOpMetrics) IncrementMessagesDropped()
- func (n *NoOpMetrics) IncrementMessagesReceived()
- func (n *NoOpMetrics) IncrementMessagesSent(count int)
- func (n *NoOpMetrics) IncrementRoomJoins()
- func (n *NoOpMetrics) IncrementRoomLeaves()
- func (n *NoOpMetrics) IncrementRooms()
- func (n *NoOpMetrics) RecordBroadcastDuration(duration time.Duration)
- func (n *NoOpMetrics) RecordLatency(duration time.Duration)
- func (n *NoOpMetrics) RecordMessageSize(size int)
- type Option
- func WithAdapter(adapter Adapter) Option
- func WithConfig(config Config) Option
- func WithDrainTimeout(timeout time.Duration) Option
- func WithDropPolicy(policy DropPolicy) Option
- func WithHookTimeout(timeout time.Duration) Option
- func WithHooks(hooks Hooks) Option
- func WithLimits(limits Limits) Option
- func WithLogger(logger Logger) Option
- func WithMessageHandler(fn func(*Client, *Message) error) Option
- func WithMetrics(metrics MetricsCollector) Option
- func WithNodeID(id string) Option
- func WithParallelBroadcast(batchSize int) Optiondeprecated
- func WithParallelBroadcastWorkers(n int) Optiondeprecated
- func WithPresence(interval time.Duration) Option
- func WithoutHandlerLatency() Option
- type Room
- type Router
- type UpgradeOption
Examples ¶
- DefaultConfig
- DefaultLimits
- Hub.Alive
- Hub.GlobalClientCount
- Hub.GlobalRoomCount
- Hub.HandleHTTP
- Hub.Health
- Hub.HealthHandler
- Hub.Ready
- Hub.ReadyHandler
- NewDebugMetrics
- NewHub
- NewHub (WithOptions)
- NewJSONMessage
- NewMessage
- NewMiddlewareChain
- NewRouter
- WithDropPolicy
- WithHookTimeout
- WithHooks
- WithNodeID
- WithParallelBroadcast
- WithoutHandlerLatency
Constants ¶
const ( AdapterBroadcast = "broadcast" AdapterBroadcastExcept = "broadcast_except" AdapterRoom = "room" AdapterRoomExcept = "room_except" AdapterUser = "user" AdapterClient = "client" AdapterPresence = "presence" )
Adapter message type constants identify the operation being relayed.
Variables ¶
var ( // Connection errors ErrConnectionClosed = errors.New("wshub: connection closed") ErrInvalidMessage = errors.New("wshub: invalid message") ErrSendBufferFull = errors.New("wshub: send buffer full") // Client errors ErrClientNotFound = errors.New("wshub: client not found") // Room errors ErrEmptyRoomName = errors.New("wshub: empty room name") ErrRoomNotFound = errors.New("wshub: room not found") ErrAlreadyInRoom = errors.New("wshub: client already in room") ErrNotInRoom = errors.New("wshub: client not in room") ErrRoomFull = errors.New("wshub: room is full") // Limit errors ErrMaxConnectionsReached = errors.New("wshub: max connections reached") ErrMaxRoomsReached = errors.New("wshub: max rooms per client reached") // ErrRateLimitExceeded is provided for use in application hooks and // handlers. The library's internal rate limiter drops messages silently // without returning this error. ErrRateLimitExceeded = errors.New("wshub: rate limit exceeded") ErrMaxUserConnectionsReached = errors.New("wshub: max connections per user reached") // Hub state errors ErrHubNotStarted = errors.New("wshub: hub not started") ErrHubDraining = errors.New("wshub: hub is draining") ErrHubStopped = errors.New("wshub: hub is stopped") // Authentication errors — provided for use in BeforeConnect hooks and // application-level handlers. The library does not return these directly. ErrAuthenticationFailed = errors.New("wshub: authentication failed") )
Sentinel errors for WebSocket operations.
Functions ¶
func AllowAllOrigins ¶
AllowAllOrigins is a CheckOrigin function that allows all origins.
func AllowOrigins ¶
AllowOrigins returns a CheckOrigin function that allows specific origins. Requests without an Origin header are allowed (see AllowSameOrigin for rationale).
func AllowSameOrigin ¶
AllowSameOrigin is a CheckOrigin function that only allows same-origin requests. It parses the Origin header as a URL and compares the host (including port) against the request's Host header, handling mismatched ports correctly.
Requests without an Origin header are allowed because non-browser clients (mobile apps, CLI tools) typically omit it. If your threat model requires rejecting originless requests, use a custom CheckOrigin function.
Types ¶
type Adapter ¶ added in v1.1.0
type Adapter interface {
// Publish sends a message to all other nodes.
Publish(ctx context.Context, msg AdapterMessage) error
// Subscribe begins receiving messages from other nodes. The provided
// handler is invoked for every message received. Subscribe must not
// block; it should spawn its own goroutine(s) internally.
// The context controls the subscription lifetime — cancelling it
// stops the subscriber.
Subscribe(ctx context.Context, handler func(AdapterMessage)) error
// Close shuts down the adapter, releasing all resources.
Close() error
}
Adapter enables multi-node Hub communication through a shared message bus. When set via WithAdapter, every public broadcast and send method publishes to the adapter after delivering locally, so that other nodes can relay the message to their own clients.
Thread safety requirements:
- Publish must be safe for concurrent calls from multiple goroutines.
- Subscribe is called once by Hub.Run and must not be called concurrently.
- Publish and Subscribe may be called concurrently with each other.
- Close may be called concurrently with Publish; implementations should handle this gracefully (e.g., return an error after close).
type AdapterMessage ¶ added in v1.1.0
type AdapterMessage struct {
// NodeID identifies the originating hub node (used for deduplication).
NodeID string `json:"node_id"`
// Type identifies the operation. Use the Adapter* constants.
Type string `json:"type"`
// Room is the target room name (for room-scoped operations).
Room string `json:"room,omitempty"`
// UserID is the target user (for SendToUser).
UserID string `json:"user_id,omitempty"`
// ClientID is the target client (for SendToClient).
ClientID string `json:"client_id,omitempty"`
// ExceptClientIDs lists client IDs to exclude from delivery.
ExceptClientIDs []string `json:"except,omitempty"`
// MsgType is the WebSocket message type (TextMessage or BinaryMessage).
MsgType int `json:"msg_type"`
// Data is the raw message payload.
Data []byte `json:"data"`
}
AdapterMessage is the wire format for inter-node messages.
type Client ¶
type Client struct {
// ID is the unique identifier for this client.
ID string
// contains filtered or unexported fields
}
Client represents a WebSocket client connection.
func (*Client) CloseWithCode ¶
CloseWithCode closes the client connection with a specific close code and reason. It uses mu+closed rather than sync.Once because it needs to atomically set closeCode/closeReason and the closed flag before closing the send channel.
func (*Client) ConnectedAt ¶
ConnectedAt returns the time when this client connected.
func (*Client) DeleteMetadata ¶
DeleteMetadata removes a metadata value.
func (*Client) GetMetadata ¶
GetMetadata returns a metadata value.
func (*Client) Request ¶
Request returns the HTTP request that initiated this WebSocket connection. Use it to access headers, query params, remote address, and other request data.
func (*Client) SendBinary ¶
SendBinary sends a binary message to the client.
func (*Client) SendMessage ¶
func (c *Client) SendMessage(msgType MessageType, data []byte) error
SendMessage sends a message with the specified type. The behavior when the send buffer is full depends on the hub's DropPolicy.
func (*Client) SendMessageWithContext ¶ added in v1.1.0
func (c *Client) SendMessageWithContext(ctx context.Context, msgType MessageType, data []byte) (err error)
SendMessageWithContext sends a message with the specified type and context support. It blocks until the message is enqueued or the context is cancelled.
Unlike SendMessage, this method does not apply the hub's DropPolicy. When the send buffer is full it waits for space rather than dropping messages, giving callers explicit control over the timeout via ctx.
func (*Client) SendRawJSON ¶ added in v1.1.3
SendRawJSON sends pre-encoded JSON data as a text message to the client. Use this instead of SendJSON when the JSON is already marshaled to avoid redundant serialization.
func (*Client) SendWithContext ¶
SendWithContext sends a text message with context support. It blocks until the message is enqueued or the context is cancelled.
func (*Client) SetMetadata ¶
SetMetadata sets a metadata value.
type Config ¶
type Config struct {
// ReadBufferSize is the size of the read buffer (default: 1024).
ReadBufferSize int
// WriteBufferSize is the size of the write buffer (default: 1024).
WriteBufferSize int
// WriteWait is the time allowed to write a message (default: 10s).
WriteWait time.Duration
// PongWait is the time allowed to read the next pong message (default: 60s).
PongWait time.Duration
// PingPeriod is the period between pings (default: 54s, must be < PongWait).
PingPeriod time.Duration
// MaxMessageSize is the maximum message size allowed (default: 512KB).
MaxMessageSize int64
// SendChannelSize is the size of the send channel buffer (default: 256).
SendChannelSize int
// EnableCompression enables per-message compression (default: false).
EnableCompression bool
// CoalesceWrites batches queued text messages into a single WebSocket
// frame separated by newline bytes (\n), reducing syscalls under high
// throughput. Binary messages are always sent as individual frames.
// Receivers must split coalesced frames on \n. Default: false.
CoalesceWrites bool
// CheckOrigin is a function to validate the request origin.
CheckOrigin func(r *http.Request) bool
// Subprotocols specifies the server's supported protocols.
Subprotocols []string
}
Config holds configuration for WebSocket connections.
func DefaultConfig ¶
func DefaultConfig() Config
DefaultConfig returns a default WebSocket configuration.
Example ¶
package main
import (
"fmt"
"github.com/KARTIKrocks/wshub"
)
func main() {
config := wshub.DefaultConfig()
fmt.Println("compression:", config.EnableCompression)
custom := config.
WithMaxMessageSize(1024).
WithCompression(true)
fmt.Println("custom compression:", custom.EnableCompression)
}
Output: compression: false custom compression: true
func (Config) WithBufferSizes ¶
WithBufferSizes returns a new config with the specified buffer sizes.
func (Config) WithCheckOrigin ¶
WithCheckOrigin returns a new config with a custom origin checker.
func (Config) WithCoalesceWrites ¶ added in v1.3.0
WithCoalesceWrites returns a new config with write coalescing enabled/disabled.
func (Config) WithCompression ¶
WithCompression returns a new config with compression enabled/disabled.
func (Config) WithMaxMessageSize ¶
WithMaxMessageSize returns a new config with the specified max message size.
func (Config) WithSendChannelSize ¶
WithSendChannelSize returns a new config with the specified send channel size.
func (Config) WithSubprotocols ¶
WithSubprotocols returns a new config with the specified subprotocols.
type DebugMetrics ¶
type DebugMetrics struct {
// contains filtered or unexported fields
}
DebugMetrics is a thread-safe in-memory MetricsCollector for development and testing. Use Stats() to read a snapshot or String() to print a summary.
Usage:
m := wshub.NewDebugMetrics() hub := wshub.NewHub(wshub.WithMetrics(m)) ... fmt.Println(m) // pretty-print summary stats := m.Stats() // programmatic access
func NewDebugMetrics ¶
func NewDebugMetrics() *DebugMetrics
NewDebugMetrics creates a new DebugMetrics instance.
Example ¶
package main
import (
"fmt"
"github.com/KARTIKrocks/wshub"
)
func main() {
metrics := wshub.NewDebugMetrics()
metrics.IncrementConnections()
metrics.IncrementMessagesReceived()
metrics.RecordMessageSize(128)
stats := metrics.Stats()
fmt.Println("connections:", stats.ActiveConnections)
fmt.Println("messages:", stats.TotalMessagesRecv)
}
Output: connections: 1 messages: 1
func (*DebugMetrics) DecrementConnections ¶
func (d *DebugMetrics) DecrementConnections()
func (*DebugMetrics) DecrementRooms ¶ added in v1.5.0
func (d *DebugMetrics) DecrementRooms()
func (*DebugMetrics) IncrementConnections ¶
func (d *DebugMetrics) IncrementConnections()
func (*DebugMetrics) IncrementErrors ¶
func (d *DebugMetrics) IncrementErrors(errorType string)
func (*DebugMetrics) IncrementMessagesDropped ¶ added in v1.5.0
func (d *DebugMetrics) IncrementMessagesDropped()
func (*DebugMetrics) IncrementMessagesReceived ¶ added in v1.5.0
func (d *DebugMetrics) IncrementMessagesReceived()
func (*DebugMetrics) IncrementMessagesSent ¶ added in v1.5.0
func (d *DebugMetrics) IncrementMessagesSent(count int)
func (*DebugMetrics) IncrementRoomJoins ¶
func (d *DebugMetrics) IncrementRoomJoins()
func (*DebugMetrics) IncrementRoomLeaves ¶
func (d *DebugMetrics) IncrementRoomLeaves()
func (*DebugMetrics) IncrementRooms ¶ added in v1.5.0
func (d *DebugMetrics) IncrementRooms()
func (*DebugMetrics) RecordBroadcastDuration ¶ added in v1.5.0
func (d *DebugMetrics) RecordBroadcastDuration(duration time.Duration)
func (*DebugMetrics) RecordLatency ¶
func (d *DebugMetrics) RecordLatency(duration time.Duration)
func (*DebugMetrics) RecordMessageSize ¶
func (d *DebugMetrics) RecordMessageSize(size int)
func (*DebugMetrics) Reset ¶
func (d *DebugMetrics) Reset()
Reset zeroes all counters and resets the uptime clock.
func (*DebugMetrics) Stats ¶
func (d *DebugMetrics) Stats() DebugStats
Stats returns a point-in-time snapshot of all metrics.
func (*DebugMetrics) String ¶
func (d *DebugMetrics) String() string
String returns a human-readable summary of all metrics. Implements fmt.Stringer so it prints naturally with fmt.Println(m).
type DebugStats ¶
type DebugStats struct {
ActiveConnections int64
TotalConnections int64
TotalMessagesRecv int64
TotalMessagesSent int64
TotalDropped int64
TotalMessageBytes int64
TotalRoomJoins int64
TotalRoomLeaves int64
ActiveRooms int64
AvgLatency time.Duration
AvgBroadcast time.Duration
Errors map[string]int64
Uptime time.Duration
}
DebugStats is a point-in-time snapshot returned by DebugMetrics.Stats().
type DropPolicy ¶ added in v1.1.0
type DropPolicy int
DropPolicy controls what happens when a client's send buffer is full.
const ( // DropNewest drops the new message when the send buffer is full. // This is the default and matches the original behavior. DropNewest DropPolicy = iota // DropOldest evicts the oldest queued message to make room for the new one. // This ensures the client always receives the most recent data, at the cost // of losing older messages. DropOldest )
type HandlerFunc ¶
HandlerFunc is a function that handles WebSocket messages.
type HealthStatus ¶ added in v1.4.0
type HealthStatus struct {
// Alive is true when the Run() goroutine is executing.
Alive bool
// Ready is true when the hub is alive and accepting new connections.
Ready bool
// State is the hub's lifecycle state as a human-readable string
// ("running", "draining", or "stopped").
State string
// Uptime is how long the Run() goroutine has been executing.
// Zero if Run() has not been called.
Uptime time.Duration
// Clients is the current number of connected clients.
Clients int
}
HealthStatus is a point-in-time snapshot of hub health, suitable for liveness and readiness probes.
type Hooks ¶
type Hooks struct {
// BeforeConnect is called before upgrading the connection.
// Return an error to reject the connection.
BeforeConnect func(*http.Request) error
// AfterConnect is called after a client successfully connects.
// It runs in its own goroutine, so the client may receive messages
// before this callback returns.
AfterConnect func(*Client)
// BeforeDisconnect is called before a client disconnects.
// The hook runs in a goroutine with a timeout (default 5s, configurable
// via WithHookTimeout). If the hook does not complete within the timeout
// the hub proceeds with the disconnect; the goroutine is NOT cancelled
// and will continue running until it returns. Keep this hook fast to
// avoid leaking goroutines.
BeforeDisconnect func(*Client)
// AfterDisconnect is called after a client disconnects.
AfterDisconnect func(*Client)
// BeforeMessage is called before processing a message.
// Can modify the message or return an error to reject it.
BeforeMessage func(*Client, *Message) (*Message, error)
// AfterMessage is called after processing a message.
AfterMessage func(*Client, *Message, error)
// OnError is called when an error occurs.
OnError func(*Client, error)
// OnSendDropped is called when a message is dropped because the client's
// send buffer is full. The application can use this to decide whether to
// disconnect the slow client, log the event, or queue the data externally.
// The hook is called synchronously in the sender's goroutine — keep it
// fast to avoid blocking broadcasts.
OnSendDropped func(client *Client, data []byte)
// BeforeRoomJoin is called before a client joins a room.
// Return an error to prevent joining.
BeforeRoomJoin func(*Client, string) error
// AfterRoomJoin is called after a client joins a room.
AfterRoomJoin func(*Client, string)
// BeforeRoomLeave is called before a client leaves a room.
BeforeRoomLeave func(*Client, string)
// AfterRoomLeave is called after a client leaves a room.
AfterRoomLeave func(*Client, string)
}
Hooks defines lifecycle callbacks for WebSocket operations.
type Hub ¶
type Hub struct {
// contains filtered or unexported fields
}
Hub maintains the set of active clients and broadcasts messages.
Lock ordering (acquire in this order to prevent deadlocks):
mu (hub clients) → roomsMu → Room.mu → Client.mu → userIndexMu
Not all paths acquire every lock; the rule is that when multiple locks from this list are held simultaneously, the earlier one must be acquired first. Individual locks that are never held together (e.g. Client.mu and userIndexMu acquired sequentially, not nested) are safe regardless of order.
func NewHub ¶
NewHub creates a new WebSocket hub.
Example ¶
package main
import (
"fmt"
"github.com/KARTIKrocks/wshub"
)
func main() {
hub := wshub.NewHub()
fmt.Println("clients:", hub.ClientCount())
}
Output: clients: 0
Example (WithOptions) ¶
package main
import (
"fmt"
"github.com/KARTIKrocks/wshub"
)
func main() {
hub := wshub.NewHub(
wshub.WithConfig(wshub.DefaultConfig().WithMaxMessageSize(4096)),
wshub.WithLimits(wshub.DefaultLimits().WithMaxConnections(1000)),
wshub.WithLogger(&wshub.NoOpLogger{}),
wshub.WithMetrics(wshub.NewDebugMetrics()),
)
fmt.Println("clients:", hub.ClientCount())
}
Output: clients: 0
func (*Hub) Alive ¶ added in v1.4.0
Alive reports whether the Hub.Run goroutine is currently executing. Returns false before Run is called or after it exits. The read is a single atomic load — safe for concurrent use on hot paths.
Example ¶
package main
import (
"fmt"
"github.com/KARTIKrocks/wshub"
)
func main() {
hub := wshub.NewHub()
fmt.Println("alive before Run:", hub.Alive())
}
Output: alive before Run: false
func (*Hub) Broadcast ¶
Broadcast sends a text message to all connected clients. In multi-node mode the message is also relayed to other nodes via the adapter.
func (*Hub) BroadcastBinary ¶
BroadcastBinary sends a binary message to all connected clients. In multi-node mode the message is also relayed to other nodes via the adapter.
func (*Hub) BroadcastBinaryExcept ¶ added in v1.1.0
BroadcastBinaryExcept sends a binary message to all clients except those specified. In multi-node mode the message is also relayed to other nodes via the adapter.
func (*Hub) BroadcastBinaryToRoom ¶ added in v1.1.0
BroadcastBinaryToRoom sends a binary message to all clients in a room. In multi-node mode the message is also relayed to other nodes via the adapter.
func (*Hub) BroadcastBinaryToRoomExcept ¶ added in v1.1.0
BroadcastBinaryToRoomExcept sends a binary message to all clients in a room except those specified. In multi-node mode the message is also relayed to other nodes via the adapter.
func (*Hub) BroadcastExcept ¶
BroadcastExcept sends a text message to all clients except those specified. In multi-node mode the message is also relayed to other nodes via the adapter.
func (*Hub) BroadcastJSON ¶
BroadcastJSON sends a JSON message to all connected clients.
func (*Hub) BroadcastRawJSON ¶ added in v1.1.3
BroadcastRawJSON sends pre-encoded JSON data as a text message to all connected clients. Use this instead of BroadcastJSON when the JSON is already marshaled to avoid redundant serialization. In multi-node mode the message is also relayed to other nodes via the adapter.
func (*Hub) BroadcastText ¶
BroadcastText sends a text message to all connected clients.
func (*Hub) BroadcastToRoom ¶
BroadcastToRoom sends a text message to all clients in a room. In multi-node mode the message is also relayed to other nodes via the adapter so that room members on other nodes receive it.
func (*Hub) BroadcastToRoomExcept ¶
BroadcastToRoomExcept sends a text message to all clients in a room except those specified. In multi-node mode the message is also relayed to other nodes via the adapter.
func (*Hub) BroadcastToRoomWithContext ¶ added in v1.1.0
BroadcastToRoomWithContext sends a text message to all clients in a room with context support. Returns ctx.Err() if the context is cancelled mid-broadcast.
func (*Hub) BroadcastWithContext ¶
BroadcastWithContext sends a message to all clients with context support. If the context is cancelled mid-broadcast, remaining local clients are skipped but the adapter publish still fires so other nodes can deliver. The returned error (if any) is the context error.
func (*Hub) ClientCount ¶
ClientCount returns the number of connected clients. Uses an atomic counter — no locking required.
func (*Hub) Clients ¶
Clients returns all connected clients using the lock-free broadcast snapshot, avoiding contention on h.mu.
func (*Hub) Drain ¶ added in v1.2.0
Drain initiates graceful connection draining. It stops accepting new connections (Hub.UpgradeConnection returns HTTP 503) and waits for all existing connections to disconnect or for the context to expire.
During drain, idle connections whose send buffers have been empty for the configured drain timeout (see WithDrainTimeout) are proactively closed with CloseGoingAway (1001). This prevents indefinite waiting for clients that are connected but doing nothing.
Drain returns nil when all clients have disconnected, or the context error if the context expires first. Calling Drain on an already-draining hub blocks on the same completion signal. Calling Drain after Hub.Shutdown returns immediately.
Typical usage in a Kubernetes preStop hook:
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() hub.Drain(ctx) // stop new connections, wait for existing ones hub.Shutdown(ctx) // force-close anything remaining
func (*Hub) GetClientByUserID ¶
GetClientByUserID returns a client by user ID.
func (*Hub) GetClientsByUserID ¶
GetClientsByUserID returns all clients for a user ID.
func (*Hub) GlobalClientCount ¶ added in v1.1.0
GlobalClientCount returns the total number of connected clients across all nodes. In single-node mode or when presence is not enabled it returns the local count.
Example ¶
package main
import (
"fmt"
"github.com/KARTIKrocks/wshub"
)
func main() {
// Without presence, GlobalClientCount returns local count.
hub := wshub.NewHub()
fmt.Println("global:", hub.GlobalClientCount())
}
Output: global: 0
func (*Hub) GlobalRoomCount ¶ added in v1.1.0
GlobalRoomCount returns the total number of clients in a room across all nodes. In single-node mode or when presence is not enabled it returns the local room count.
Example ¶
package main
import (
"fmt"
"github.com/KARTIKrocks/wshub"
)
func main() {
// Without presence, GlobalRoomCount returns local room count.
hub := wshub.NewHub()
fmt.Println("room count:", hub.GlobalRoomCount("lobby"))
}
Output: room count: 0
func (*Hub) HandleHTTP ¶
func (h *Hub) HandleHTTP() http.HandlerFunc
HandleHTTP returns an HTTP handler that upgrades connections to WebSocket. Upgrade errors are logged via the hub's logger.
Example ¶
package main
import (
"fmt"
"github.com/KARTIKrocks/wshub"
)
func main() {
hub := wshub.NewHub()
handler := hub.HandleHTTP()
fmt.Println("handler is nil:", handler == nil)
}
Output: handler is nil: false
func (*Hub) Health ¶ added in v1.4.0
func (h *Hub) Health() HealthStatus
Health returns a point-in-time HealthStatus snapshot. All reads are lock-free atomic loads.
Example ¶
package main
import (
"fmt"
"github.com/KARTIKrocks/wshub"
)
func main() {
hub := wshub.NewHub()
hs := hub.Health()
fmt.Println("alive:", hs.Alive)
fmt.Println("ready:", hs.Ready)
}
Output: alive: false ready: false
func (*Hub) HealthHandler ¶ added in v1.4.0
func (h *Hub) HealthHandler() http.HandlerFunc
HealthHandler returns an HTTP handler that reports the hub's liveness status. Returns 200 OK when the Hub.Run goroutine is alive, 503 Service Unavailable otherwise. The response body is a JSON object with the same fields as HealthStatus.
Typical usage with Kubernetes:
http.Handle("/healthz", hub.HealthHandler())
Example ¶
package main
import (
"fmt"
"github.com/KARTIKrocks/wshub"
)
func main() {
hub := wshub.NewHub()
handler := hub.HealthHandler()
fmt.Println("handler is nil:", handler == nil)
}
Output: handler is nil: false
func (*Hub) IsDraining ¶ added in v1.2.0
IsDraining reports whether the hub is in the draining state.
func (*Hub) IsRunning ¶ added in v1.2.0
IsRunning reports whether the hub is in the running state and accepting new connections. Returns false when draining or stopped.
func (*Hub) LeaveAllRooms ¶
LeaveAllRooms removes a client from all rooms, firing BeforeRoomLeave/AfterRoomLeave hooks for each room.
func (*Hub) NodeID ¶ added in v1.1.0
NodeID returns this hub's unique node identifier. In multi-node setups each hub has a distinct ID used for message deduplication.
func (*Hub) Ready ¶ added in v1.4.0
Ready reports whether the hub is alive and in the StateRunning state, meaning it can accept and process new connections. Returns false before Hub.Run is called, while draining, or after shutdown.
Example ¶
package main
import (
"fmt"
"github.com/KARTIKrocks/wshub"
)
func main() {
hub := wshub.NewHub()
fmt.Println("ready before Run:", hub.Ready())
}
Output: ready before Run: false
func (*Hub) ReadyHandler ¶ added in v1.4.0
func (h *Hub) ReadyHandler() http.HandlerFunc
ReadyHandler returns an HTTP handler that reports the hub's readiness status. Returns 200 OK when the hub is alive and in StateRunning (accepting connections), 503 Service Unavailable otherwise.
Typical usage with Kubernetes:
http.Handle("/readyz", hub.ReadyHandler())
Example ¶
package main
import (
"fmt"
"github.com/KARTIKrocks/wshub"
)
func main() {
hub := wshub.NewHub()
handler := hub.ReadyHandler()
fmt.Println("handler is nil:", handler == nil)
}
Output: handler is nil: false
func (*Hub) RoomClients ¶
RoomClients returns all clients in a room.
func (*Hub) RoomExists ¶
RoomExists checks if a room exists.
func (*Hub) SendBinaryToClient ¶ added in v1.1.0
SendBinaryToClient sends a binary message to a specific client by ID. In multi-node mode the message is also relayed to other nodes via the adapter.
func (*Hub) SendBinaryToUser ¶ added in v1.1.0
SendBinaryToUser sends a binary message to all clients of a specific user. In multi-node mode the message is also relayed to other nodes via the adapter.
func (*Hub) SendToClient ¶
SendToClient sends a text message to a specific client by ID. In multi-node mode the message is also relayed to other nodes via the adapter, allowing delivery to clients connected to a different node.
func (*Hub) SendToClientWithContext ¶ added in v1.1.0
SendToClientWithContext sends a text message to a specific client by ID with context support. It blocks until the message is enqueued or the context is cancelled. In multi-node mode the message is also relayed via the adapter.
func (*Hub) SendToUser ¶
SendToUser sends a text message to all clients of a specific user. In multi-node mode the message is also relayed to other nodes via the adapter.
func (*Hub) SendToUserWithContext ¶ added in v1.1.0
SendToUserWithContext sends a text message to all clients of a specific user with context support. It blocks until messages are enqueued or the context is cancelled. In multi-node mode the message is also relayed via the adapter.
func (*Hub) Shutdown ¶
Shutdown gracefully shuts down the hub. It force-closes all remaining connections and waits for goroutines to exit. If the hub is draining, Shutdown unblocks any pending Hub.Drain call.
func (*Hub) State ¶ added in v1.2.0
State returns the current lifecycle state of the hub. Safe for concurrent use; the read is a single atomic load.
func (*Hub) UpgradeConnection ¶
func (h *Hub) UpgradeConnection(w http.ResponseWriter, r *http.Request, opts ...UpgradeOption) (*Client, error)
UpgradeConnection upgrades an HTTP connection to WebSocket.
type HubState ¶ added in v1.2.0
type HubState int32
HubState represents the lifecycle state of a Hub.
const ( // StateRunning means the hub is accepting new connections and processing // messages normally. StateRunning HubState = iota // StateDraining means the hub has stopped accepting new connections // (returning HTTP 503) but is allowing existing connections to finish // their in-flight messages. Initiated by [Hub.Drain]. StateDraining // StateStopped means the hub has been shut down via [Hub.Shutdown]. // All connections are closed and the Run loop has exited. StateStopped )
type Limits ¶
type Limits struct {
// MaxConnections is the maximum number of concurrent connections.
// 0 means unlimited.
MaxConnections int
// MaxConnectionsPerUser is the maximum number of connections per user ID.
// 0 means unlimited.
MaxConnectionsPerUser int
// MaxRoomsPerClient is the maximum number of rooms a client can join.
// 0 means unlimited.
MaxRoomsPerClient int
// MaxClientsPerRoom is the maximum number of clients in a room.
// 0 means unlimited.
MaxClientsPerRoom int
// MaxMessageRate is the maximum messages per second per client.
// 0 means unlimited.
MaxMessageRate int
}
Limits defines various limits for WebSocket operations.
func DefaultLimits ¶
func DefaultLimits() Limits
DefaultLimits returns default limits (all unlimited).
Example ¶
package main
import (
"fmt"
"github.com/KARTIKrocks/wshub"
)
func main() {
limits := wshub.DefaultLimits()
fmt.Println("max connections:", limits.MaxConnections)
custom := limits.
WithMaxConnections(5000).
WithMaxRoomsPerClient(10)
fmt.Println("custom max connections:", custom.MaxConnections)
fmt.Println("custom max rooms:", custom.MaxRoomsPerClient)
}
Output: max connections: 0 custom max connections: 5000 custom max rooms: 10
func (Limits) WithMaxClientsPerRoom ¶
WithMaxClientsPerRoom sets the maximum clients per room limit.
func (Limits) WithMaxConnections ¶
WithMaxConnections sets the maximum connections limit.
func (Limits) WithMaxConnectionsPerUser ¶
WithMaxConnectionsPerUser sets the maximum connections per user limit.
func (Limits) WithMaxMessageRate ¶
WithMaxMessageRate sets the maximum message rate limit.
func (Limits) WithMaxRoomsPerClient ¶
WithMaxRoomsPerClient sets the maximum rooms per client limit.
type Logger ¶
type Logger interface {
Debug(msg string, args ...any)
Info(msg string, args ...any)
Warn(msg string, args ...any)
Error(msg string, args ...any)
}
Logger is an interface for logging. Applications can plug in their own logger (zap, logrus, slog, etc.)
type Message ¶
type Message struct {
// Type is the message type (text, binary, etc.).
Type MessageType
// Data is the raw message data.
Data []byte
// ClientID is the ID of the client that sent the message.
ClientID string
// Time is when the message was received.
Time time.Time
}
Message represents a WebSocket message.
func NewBinaryMessage ¶
NewBinaryMessage creates a new binary message.
func NewJSONMessage ¶
NewJSONMessage creates a new JSON message.
Example ¶
package main
import (
"fmt"
"github.com/KARTIKrocks/wshub"
)
func main() {
payload := map[string]string{"event": "chat", "text": "hello"}
msg, err := wshub.NewJSONMessage(payload)
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Println("has data:", len(msg.Data) > 0)
var decoded map[string]string
msg.JSON(&decoded)
fmt.Println("event:", decoded["event"])
}
Output: has data: true event: chat
func NewMessage ¶
NewMessage creates a new text message.
Example ¶
package main
import (
"fmt"
"github.com/KARTIKrocks/wshub"
)
func main() {
msg := wshub.NewMessage([]byte("hello world"))
fmt.Println("text:", msg.Text())
fmt.Println("type:", msg.Type)
}
Output: text: hello world type: 1
func NewRawJSONMessage ¶ added in v1.1.3
NewRawJSONMessage creates a text message from pre-encoded JSON data. The caller is responsible for ensuring data is valid JSON. This avoids the marshaling cost of NewJSONMessage when the JSON is already available (e.g., cached or encoded once for fan-out).
func NewTextMessage ¶
NewTextMessage creates a new text message from a string.
type MessageType ¶
type MessageType int
MessageType represents the type of WebSocket message.
const ( TextMessage MessageType = websocket.TextMessage BinaryMessage MessageType = websocket.BinaryMessage CloseMessage MessageType = websocket.CloseMessage PingMessage MessageType = websocket.PingMessage PongMessage MessageType = websocket.PongMessage )
type MetricsCollector ¶
type MetricsCollector interface {
// Connections
IncrementConnections()
DecrementConnections()
// Messages
IncrementMessagesReceived()
IncrementMessagesSent(count int)
IncrementMessagesDropped()
// Observations
RecordMessageSize(size int)
RecordLatency(duration time.Duration)
RecordBroadcastDuration(duration time.Duration)
// Rooms
IncrementRoomJoins()
IncrementRoomLeaves()
IncrementRooms()
DecrementRooms()
// Errors
IncrementErrors(errorType string)
}
MetricsCollector is an interface for collecting metrics. Applications can implement this with Prometheus, StatsD, etc.
type Middleware ¶
type Middleware func(HandlerFunc) HandlerFunc
Middleware wraps a HandlerFunc to add additional functionality.
func LoggingMiddleware ¶
func LoggingMiddleware(logger Logger) Middleware
LoggingMiddleware logs incoming messages.
func MetricsMiddleware ¶
func MetricsMiddleware(metrics MetricsCollector) Middleware
MetricsMiddleware records handler-level metrics for message processing. Note: message count and size are already tracked by the readPump, so this middleware only records handler errors and processing latency to avoid double-counting those. However, processing latency is also recorded internally by the hub when a message handler is set via WithMessageHandler. If you use MetricsMiddleware inside a chain passed to WithMessageHandler, add WithoutHandlerLatency() to your hub options to disable the hub's built-in latency recording and avoid double-counting.
func RecoveryMiddleware ¶
func RecoveryMiddleware(logger Logger) Middleware
RecoveryMiddleware recovers from panics in message handlers.
type MiddlewareChain ¶
type MiddlewareChain struct {
// contains filtered or unexported fields
}
MiddlewareChain manages a chain of middlewares. All mutations (Use) must happen before the first Execute call. Execute is safe for concurrent use once the chain is built.
func NewMiddlewareChain ¶
func NewMiddlewareChain(handler HandlerFunc) *MiddlewareChain
NewMiddlewareChain creates a new middleware chain with the final handler.
Example ¶
package main
import (
"fmt"
"github.com/KARTIKrocks/wshub"
)
func main() {
handler := func(c *wshub.Client, m *wshub.Message) error {
return nil
}
chain := wshub.NewMiddlewareChain(handler).
Use(wshub.RecoveryMiddleware(&wshub.NoOpLogger{})).
Build()
fmt.Println("chain built:", chain != nil)
}
Output: chain built: true
func (*MiddlewareChain) Build ¶
func (m *MiddlewareChain) Build() *MiddlewareChain
Build pre-computes the composed handler chain and caches it. Subsequent calls to Execute will use the cached handler for better performance. Must not be called concurrently with Use. Returns the chain for method chaining.
func (*MiddlewareChain) Execute ¶
func (m *MiddlewareChain) Execute(client *Client, msg *Message) error
Execute runs the middleware chain and final handler. Automatically builds and caches the chain on first call if Build was not called explicitly. Safe for concurrent use once the chain is built. Uses double-checked locking to ensure only one goroutine builds.
func (*MiddlewareChain) Use ¶
func (m *MiddlewareChain) Use(middleware Middleware) *MiddlewareChain
Use adds a middleware to the chain. Adding middleware invalidates any previously cached Build result. Must not be called concurrently with Execute.
type NoOpLogger ¶
type NoOpLogger struct{}
NoOpLogger is a default implementation that does nothing.
func (*NoOpLogger) Debug ¶
func (n *NoOpLogger) Debug(msg string, args ...any)
func (*NoOpLogger) Error ¶
func (n *NoOpLogger) Error(msg string, args ...any)
func (*NoOpLogger) Info ¶
func (n *NoOpLogger) Info(msg string, args ...any)
func (*NoOpLogger) Warn ¶
func (n *NoOpLogger) Warn(msg string, args ...any)
type NoOpMetrics ¶
type NoOpMetrics struct{}
NoOpMetrics is a default implementation that does nothing.
func (*NoOpMetrics) DecrementConnections ¶
func (n *NoOpMetrics) DecrementConnections()
func (*NoOpMetrics) DecrementRooms ¶ added in v1.5.0
func (n *NoOpMetrics) DecrementRooms()
func (*NoOpMetrics) IncrementConnections ¶
func (n *NoOpMetrics) IncrementConnections()
func (*NoOpMetrics) IncrementErrors ¶
func (n *NoOpMetrics) IncrementErrors(errorType string)
func (*NoOpMetrics) IncrementMessagesDropped ¶ added in v1.5.0
func (n *NoOpMetrics) IncrementMessagesDropped()
func (*NoOpMetrics) IncrementMessagesReceived ¶ added in v1.5.0
func (n *NoOpMetrics) IncrementMessagesReceived()
func (*NoOpMetrics) IncrementMessagesSent ¶ added in v1.5.0
func (n *NoOpMetrics) IncrementMessagesSent(count int)
func (*NoOpMetrics) IncrementRoomJoins ¶
func (n *NoOpMetrics) IncrementRoomJoins()
func (*NoOpMetrics) IncrementRoomLeaves ¶
func (n *NoOpMetrics) IncrementRoomLeaves()
func (*NoOpMetrics) IncrementRooms ¶ added in v1.5.0
func (n *NoOpMetrics) IncrementRooms()
func (*NoOpMetrics) RecordBroadcastDuration ¶ added in v1.5.0
func (n *NoOpMetrics) RecordBroadcastDuration(duration time.Duration)
func (*NoOpMetrics) RecordLatency ¶
func (n *NoOpMetrics) RecordLatency(duration time.Duration)
func (*NoOpMetrics) RecordMessageSize ¶
func (n *NoOpMetrics) RecordMessageSize(size int)
type Option ¶
type Option func(*Hub)
Option configures a Hub during construction.
func WithAdapter ¶ added in v1.1.0
WithAdapter sets the multi-node adapter for cross-node message delivery. When configured, every broadcast and targeted send is relayed to other nodes through the adapter, enabling horizontal scaling behind a load balancer.
func WithConfig ¶
WithConfig sets the WebSocket configuration.
func WithDrainTimeout ¶ added in v1.2.0
WithDrainTimeout sets the maximum time an idle connection can remain open after Hub.Drain is called. Connections whose send buffers have been empty for this duration are proactively closed with CloseGoingAway (1001).
Default: 30s. Set to 0 to disable the idle connection reaper entirely, relying solely on natural client disconnection during drain.
func WithDropPolicy ¶ added in v1.1.0
func WithDropPolicy(policy DropPolicy) Option
WithDropPolicy sets the behavior when a client's send buffer is full. The default is DropNewest which discards the new message. DropOldest evicts the oldest queued message to make room for the new one.
Example ¶
package main
import (
"fmt"
"github.com/KARTIKrocks/wshub"
)
func main() {
hub := wshub.NewHub(
wshub.WithDropPolicy(wshub.DropOldest),
wshub.WithHooks(wshub.Hooks{
OnSendDropped: func(c *wshub.Client, data []byte) {
fmt.Printf("dropped %d bytes for %s\n", len(data), c.ID)
},
}),
)
fmt.Println("drop policy hub:", hub.ClientCount())
}
Output: drop policy hub: 0
func WithHookTimeout ¶ added in v1.1.0
WithHookTimeout sets the maximum time to wait for synchronous lifecycle hooks (e.g. BeforeDisconnect) before proceeding. Default: 5s.
Example ¶
package main
import (
"fmt"
"time"
"github.com/KARTIKrocks/wshub"
)
func main() {
hub := wshub.NewHub(
wshub.WithHookTimeout(10 * time.Second),
)
fmt.Println("hub created:", hub.ClientCount())
}
Output: hub created: 0
func WithHooks ¶
WithHooks sets the lifecycle hooks for the hub.
Example ¶
package main
import (
"fmt"
"net/http"
"github.com/KARTIKrocks/wshub"
)
func main() {
hub := wshub.NewHub(
wshub.WithHooks(wshub.Hooks{
BeforeConnect: func(r *http.Request) error {
token := r.URL.Query().Get("token")
if token == "" {
return fmt.Errorf("missing token")
}
return nil
},
AfterConnect: func(c *wshub.Client) {
fmt.Println("client connected:", c.ID)
},
}),
)
fmt.Println("hub with hooks:", hub.ClientCount())
}
Output: hub with hooks: 0
func WithMessageHandler ¶
WithMessageHandler sets the hub-level message handler.
func WithMetrics ¶
func WithMetrics(metrics MetricsCollector) Option
WithMetrics sets the metrics collector for the hub.
func WithNodeID ¶ added in v1.1.0
WithNodeID sets a deterministic node identifier for this hub. By default a random UUID is generated. Setting a stable ID (e.g., hostname or pod name) makes logs and debugging easier in multi-node deployments.
Example ¶
package main
import (
"fmt"
"github.com/KARTIKrocks/wshub"
)
func main() {
hub := wshub.NewHub(
wshub.WithNodeID("pod-web-1"),
)
fmt.Println("node:", hub.NodeID())
}
Output: node: pod-web-1
func WithParallelBroadcast
deprecated
WithParallelBroadcast enables parallel broadcasting with the given batch size. batchSize determines how many clients each goroutine handles (recommended: 50-200).
Deprecated: end-to-end load tests (see `make loadtest`) show parallel dispatch is consistently slower than the default serial path — the per-call cost of the non-blocking send (RLock + defer/recover) dominates and parallel batching cannot overcome it. This option is retained for backward compatibility and may be removed in a future major version. For per-node fanout above ~5K clients, scale horizontally via the Redis or NATS adapter rather than enabling parallel broadcast.
Example ¶
package main
import (
"fmt"
"github.com/KARTIKrocks/wshub"
)
func main() {
hub := wshub.NewHub(
wshub.WithParallelBroadcast(100),
)
fmt.Println("parallel hub created:", hub.ClientCount())
}
Output: parallel hub created: 0
func WithParallelBroadcastWorkers
deprecated
added in
v1.1.2
WithParallelBroadcastWorkers sets the number of persistent worker goroutines used for parallel broadcasting. The default is runtime.NumCPU(). This option has no effect unless WithParallelBroadcast is also set.
Deprecated: see WithParallelBroadcast. Parallel broadcast is no longer recommended; this tuning option is retained for backward compatibility.
func WithPresence ¶ added in v1.1.0
WithPresence enables periodic presence broadcasting for multi-node stats. Each hub publishes its local client and room counts at the given interval, allowing GlobalClientCount and GlobalRoomCount to return cluster-wide totals.
When interval is zero, the default of 5 seconds is used. Nodes that miss 3 consecutive heartbeats are considered stale and evicted from the totals.
Presence requires an adapter to be set via WithAdapter; without one it is a no-op.
func WithoutHandlerLatency ¶ added in v1.1.0
func WithoutHandlerLatency() Option
WithoutHandlerLatency disables the hub's automatic handler latency recording. Use this when your message handler chain already includes MetricsMiddleware to avoid double-counting latency.
Example ¶
package main
import (
"fmt"
"github.com/KARTIKrocks/wshub"
)
func main() {
metrics := wshub.NewDebugMetrics()
handler := func(c *wshub.Client, m *wshub.Message) error { return nil }
chain := wshub.NewMiddlewareChain(handler).
Use(wshub.MetricsMiddleware(metrics)).
Build()
hub := wshub.NewHub(
wshub.WithMetrics(metrics),
wshub.WithMessageHandler(chain.Execute),
wshub.WithoutHandlerLatency(), // avoid double-counting with MetricsMiddleware
)
fmt.Println("latency skipped:", hub.ClientCount() == 0)
}
Output: latency skipped: true
type Room ¶
type Room struct {
// contains filtered or unexported fields
}
Room represents a chat room with its own lock for better concurrency.
type Router ¶
type Router struct {
// contains filtered or unexported fields
}
Router dispatches incoming messages to per-event handlers based on an event name extracted from each message by a user-provided extractor function.
The extractor decouples the router from any specific message format — JSON, msgpack, binary with a leading byte, or anything else.
Usage:
router := wshub.NewRouter(func(msg *wshub.Message) string {
var env struct{ Type string `json:"type"` }
json.Unmarshal(msg.Data, &env)
return env.Type
})
router.
On("chat", handleChat).
On("join", handleJoin).
On("leave", handleLeave)
hub := wshub.NewHub(wshub.WithMessageHandler(router.Handle))
All On/OnNotFound calls should be made before the hub starts running.
func NewRouter ¶
NewRouter creates a Router. The extractor is called on every incoming message to determine which registered handler to dispatch to.
Example ¶
package main
import (
"encoding/json"
"fmt"
"github.com/KARTIKrocks/wshub"
)
func main() {
router := wshub.NewRouter(func(m *wshub.Message) string {
var envelope struct {
Event string `json:"event"`
}
json.Unmarshal(m.Data, &envelope)
return envelope.Event
})
router.On("ping", func(c *wshub.Client, m *wshub.Message) error {
return c.SendText("pong")
})
router.OnNotFound(func(c *wshub.Client, m *wshub.Message) error {
return c.SendText("unknown event")
})
fmt.Println("router created")
}
Output: router created
func (*Router) Handle ¶
Handle dispatches the message to the appropriate handler. Pass this method to WithMessageHandler or use it as a HandlerFunc directly.
func (*Router) On ¶
func (r *Router) On(event string, handler HandlerFunc) *Router
On registers a handler for the given event name. Returns the router for chaining.
func (*Router) OnNotFound ¶
func (r *Router) OnNotFound(handler HandlerFunc) *Router
OnNotFound sets a fallback handler called when the extracted event name has no registered handler. If not set, unmatched events return ErrInvalidMessage.
type UpgradeOption ¶ added in v1.1.0
type UpgradeOption func(*Client)
UpgradeOption configures a single UpgradeConnection call.
func WithUserID ¶ added in v1.1.0
func WithUserID(userID string) UpgradeOption
WithUserID sets the user ID on the client atomically during connection upgrade, before the client is registered. This avoids the window where a client exists without a user ID, which can bypass MaxConnectionsPerUser.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
adapter
|
|
|
redis
module
|
|
|
cmd
|
|
|
loadtest
command
Command loadtest runs end-to-end load tests against a real wshub server with real WebSocket connections to find bottlenecks that micro-benchmarks miss.
|
Command loadtest runs end-to-end load tests against a real wshub server with real WebSocket connections to find bottlenecks that micro-benchmarks miss. |
|
examples
|
|
|
auth
command
Example: JWT authentication with wshub.
|
Example: JWT authentication with wshub. |
|
chat
command
|
|
|
metrics
command
Example: Prometheus-style metrics with wshub.
|
Example: Prometheus-style metrics with wshub. |
|
simple
command
|