WebSocket Manager (wrapsocket)
A high-level WebSocket connection management framework built on top of coder/websocket. It provides connection lifecycle management, heartbeat detection, group broadcasting, and metadata storage.
Installation
go
import "github.com/leoheung/go-patterns/net/wrapsocket"Dependencies:
github.com/coder/websocket- WebSocket implementation
Quick Start
go
package main
import (
"fmt"
"net/http"
"github.com/leoheung/go-patterns/net/wrapsocket"
)
func main() {
// Create handler with default options
handler := wrapsocket.NewDefaultHandler(nil)
// Set connection callback
handler.SetOnConnect(func(conn *wrapsocket.Conn) {
fmt.Printf("Client connected: %s\n", conn.ID)
})
// Set message callback
handler.SetOnMessage(func(conn *wrapsocket.Conn, msg *wrapsocket.Message) {
fmt.Printf("Received from %s: %s\n", conn.ID, string(msg.Data))
})
// Set disconnect callback
handler.SetOnDisconnect(func(conn *wrapsocket.Conn) {
fmt.Printf("Client disconnected: %s\n", conn.ID)
})
http.ListenAndServe(":8080", handler)
}API Reference
Handler
The Handler interface manages WebSocket upgrades and connection lifecycle.
go
// Create handler with custom WebSocket accept options
opts := &websocket.AcceptOptions{
OriginPatterns: []string{"localhost", "*.example.com"},
}
handler := wrapsocket.NewDefaultHandler(opts)Lifecycle Hooks
go
// Called when a new client connects
handler.SetOnConnect(func(conn *wrapsocket.Conn) {
// Connection is already added to manager
// Send welcome message or authenticate
})
// Called when a client disconnects
handler.SetOnDisconnect(func(conn *wrapsocket.Conn) {
// Cleanup resources
})
// Called when a message is received
handler.SetOnMessage(func(conn *wrapsocket.Conn, msg *wrapsocket.Message) {
// Handle incoming message
})
// Called when an error occurs
handler.SetOnError(func(conn *wrapsocket.Conn, err error) {
// Log or handle error
})Heartbeat Configuration
go
config := &wrapsocket.HeartbeatConfig{
Interval: 30 * time.Second, // Ping interval
Timeout: 10 * time.Second, // Ping timeout
MaxMissed: 3, // Max missed pings before disconnect
}
handler.SetHeartbeatConfig(config)Conn (Connection)
Each WebSocket connection is wrapped in a Conn struct with additional metadata.
go
// Connection properties
type Conn struct {
ID string // Auto-generated UUID
Group string // Optional group name
metadata map[string]interface{} // Custom metadata storage
}Metadata Operations
go
// Store custom data
conn.SetMetadata("user_id", "12345")
conn.SetMetadata("room", "lobby")
// Retrieve data
if val, ok := conn.GetMetadata("user_id"); ok {
userID := val.(string)
}Send Message
go
// Send to specific connection
ctx := context.Background()
err := conn.Write(ctx, websocket.MessageText, []byte("hello"))
// Check connection status
if !conn.IsClosed() {
// Safe to send
}ConnManager
Manages all active connections with thread-safe operations.
go
manager := handler.Manager()Connection Operations
go
// Get connection by ID
conn, ok := manager.Get("conn-uuid")
// Get all connections
conns := manager.GetAll()
// Get connection count
count := manager.Count()Broadcasting
go
// Broadcast to all connections
ctx := context.Background()
manager.Broadcast(ctx, websocket.MessageText, []byte("announcement"))
// Send to specific connection
manager.SendTo(ctx, "conn-uuid", websocket.MessageText, []byte("private"))Group Operations
go
// Assign group (in OnConnect callback)
handler.SetOnConnect(func(conn *wrapsocket.Conn) {
conn.Group = "room-1"
})
// Get connections by group
roomConns := manager.GetByGroup("room-1")
// Broadcast to group
manager.SendToGroup(ctx, "room-1", websocket.MessageText, []byte("room message"))Cleanup
go
// Close all connections
manager.CloseAll(websocket.StatusGoingAway, "server shutdown")Message
Message structure received from clients.
go
type Message struct {
ID string // Connection ID
Type websocket.MessageType // Message type (Text/Binary)
Data []byte // Raw message data
Timestamp time.Time // Receive time
}Complete Examples
Chat Server with Rooms
go
package main
import (
"context"
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/coder/websocket"
"github.com/leoheung/go-patterns/net/wrapsocket"
)
type ChatMessage struct {
Room string `json:"room"`
Content string `json:"content"`
}
func main() {
handler := wrapsocket.NewDefaultHandler(nil)
// Configure heartbeat
handler.SetHeartbeatConfig(&wrapsocket.HeartbeatConfig{
Interval: 30 * time.Second,
Timeout: 10 * time.Second,
MaxMissed: 3,
})
manager := handler.Manager()
handler.SetOnConnect(func(conn *wrapsocket.Conn) {
fmt.Printf("User %s joined\n", conn.ID)
})
handler.SetOnMessage(func(conn *wrapsocket.Conn, msg *wrapsocket.Message) {
var chatMsg ChatMessage
if err := json.Unmarshal(msg.Data, &chatMsg); err != nil {
return
}
// Join room
conn.Group = chatMsg.Room
// Broadcast to room
response, _ := json.Marshal(map[string]string{
"from": conn.ID,
"content": chatMsg.Content,
})
manager.SendToGroup(context.Background(), chatMsg.Room,
websocket.MessageText, response)
})
handler.SetOnDisconnect(func(conn *wrapsocket.Conn) {
fmt.Printf("User %s left room %s\n", conn.ID, conn.Group)
})
http.ListenAndServe(":8080", handler)
}Connection with Authentication
go
handler.SetOnConnect(func(conn *wrapsocket.Conn) {
// Store auth info in metadata
conn.SetMetadata("authenticated", false)
conn.SetMetadata("auth_time", time.Now())
})
handler.SetOnMessage(func(conn *wrapsocket.Conn, msg *wrapsocket.Message) {
// Check authentication
if auth, _ := conn.GetMetadata("authenticated"); !auth.(bool) {
// Handle auth message
if isValidToken(string(msg.Data)) {
conn.SetMetadata("authenticated", true)
conn.Write(context.Background(), websocket.MessageText,
[]byte(`{"type":"auth_success"}`))
}
return
}
// Process authenticated message
})Features
- Thread-Safe: All connection and manager operations are protected by mutex
- Auto UUID: Each connection gets a unique ID automatically
- Heartbeat: Configurable ping/pong with timeout detection
- Grouping: Built-in support for connection groups/rooms
- Metadata: Attach custom data to connections
- Lifecycle Hooks: Connect, disconnect, message, and error callbacks
- Graceful Shutdown: Close all connections cleanly
Notes
- The handler automatically sends a welcome message with connection ID upon connection
- Heartbeat is optional; if not configured, no ping/pong will be performed
- All callbacks are executed synchronously; use goroutines for long operations
- The manager's Broadcast skips closed connections automatically