Skip to content

websocket_en.md

maoxiaoyue edited this page May 14, 2026 · 2 revisions

WebSocket Package (pkg/websocket)

The websocket package provides a high-performance optimized WebSocket implementation for HypGo, supporting four protocols (JSON / Protobuf / FlatBuffers / MessagePack), built-in AES-256-GCM encryption and HMAC-SHA256 signing, permessage-deflate compression, WSS/TLS, broadcast and room management, with seamless integration with hypcontext.Context.

Key Features

  • Four-Protocol Serialization: Clients select serialization format during WebSocket handshake via Sec-WebSocket-Protocol sub-protocol (json, protobuf, flatbuffers, msgpack), with automatic server negotiation. Defaults to JSON with full backward compatibility.
  • AES-256-GCM Encryption: Message payloads encrypted via AES-256-GCM, using random 12-byte nonce per encryption, with Hub-level and per-client key override support.
  • HMAC-SHA256 Signing: Message integrity verified via HMAC-SHA256, detecting in-transit tampering attacks.
  • Composable Security Pipeline: Supports Sign-then-Encrypt (default) or Encrypt-then-Sign ordering, with AES and HMAC independently or simultaneously enabled.
  • permessage-deflate Compression: Configurable compression level (1-9), reducing bandwidth usage.
  • WSS/TLS Support: Standalone mode provides ListenAndServeTLS for quick secure WebSocket server startup.
  • Zero-Configuration Upgrade: Built-in Gorilla WebSocket Upgrader wrapper with standard configuration for cross-origin checks and security settings.
  • Object Pooling: Client, Room, Message, buffers, and broadcast client slices are all managed via sync.Pool. Client metadata maps are pre-allocated in the pool, and reset() uses map rebuild instead of individual delete, minimizing GC triggers under high-concurrency connections.
  • Built-in Channel and Room System: Provides centralized management via Hub. Developers can easily manage individual connection subscriptions, unsubscriptions, and broadcasts for Channels and Rooms.
  • Cross-Protocol Broadcasting: Clients in the same channel or room can use different serialization formats. During broadcast, each codec serializes at most once (lazy N-serialization), not scaling linearly with client count.
  • Health Detection: Built-in Ping/Pong heartbeat mechanism and automatic dead connection cleanup loop to maintain connection pool health.

Basic Usage

Initialize a Hub and define message and connection handling logic:

package main

import (
	"context"
	"log"

	hypcontext "github.com/maoxiaoyue/hypgo/pkg/context"
	"github.com/maoxiaoyue/hypgo/pkg/logger"
	"github.com/maoxiaoyue/hypgo/pkg/router"
	"github.com/maoxiaoyue/hypgo/pkg/websocket"
)

func main() {
	r := router.New()
	l := logger.NewLogger()

	// 1. Create Hub to manage all connections
	hub := websocket.NewHub(l, websocket.DefaultConfig)

	// 2. Define event callbacks
	hub.SetCallbacks(
		func(client *websocket.Client) {
			log.Printf("New connection joined! ID: %s, Codec: %s", client.ID, client.Codec().Name())
		},
		func(client *websocket.Client) {
			log.Printf("Connection disconnected! ID: %s", client.ID)
		},
		func(client *websocket.Client, msg *websocket.Message) {
			log.Printf("Message received! Type: %s, Data: %s", msg.Type, string(msg.Data))
		},
	)

	// 3. Start Hub's message dispatch and dead connection cleanup in background
	ctx := context.Background()
	go hub.Run(ctx)

	// 4. Define route and WebSocket connection upgrade endpoint
	r.GET("/ws", func(c *hypcontext.Context) {
		hub.ServeHTTP(c)
	})

	// Start server...
}

Four-Protocol Support (JSON / Protobuf / FlatBuffers / MessagePack)

Clients select serialization format when establishing WebSocket connections via the standard Sec-WebSocket-Protocol header:

// JavaScript client -- JSON (default)
const ws = new WebSocket("ws://localhost:8080/ws", ["json"]);

// Protobuf (binary, smallest payload)
const ws = new WebSocket("ws://localhost:8080/ws", ["protobuf"]);

// FlatBuffers (zero-copy binary)
const ws = new WebSocket("ws://localhost:8080/ws", ["flatbuffers"]);

// MessagePack (compact binary)
const ws = new WebSocket("ws://localhost:8080/ws", ["msgpack"]);

// No sub-protocol specified = auto JSON (backward compatible)
const ws = new WebSocket("ws://localhost:8080/ws");
// Go client -- using Protobuf sub-protocol
dialer := websocket.Dialer{
	Subprotocols: []string{"protobuf"},
}
conn, _, err := dialer.Dial("ws://localhost:8080/ws", nil)

Codec Interface

The Codec interface abstracts encoding/decoding differences across all serialization formats:

type Codec interface {
	Name() string                              // "json", "protobuf", "flatbuffers", "msgpack"
	Index() int                                // Stable unique index (for cache keys)
	Marshal(msg *Message) ([]byte, error)      // Serialize
	Unmarshal(data []byte, msg *Message) error // Deserialize
	WebSocketMessageType() int                 // TextMessage (JSON) or BinaryMessage (others)
}
Codec Index WebSocket Frame Characteristics
JSON 0 TextMessage Human-readable, maximum compatibility
Protobuf 1 BinaryMessage Smallest payload (manual protowire encoding)
FlatBuffers 2 BinaryMessage Zero-copy access (manual Builder API)
MessagePack 3 BinaryMessage Compact binary, JSON superset

Get the client's negotiated Codec via client.Codec():

hub.SetCallbacks(
	func(client *websocket.Client) {
		codec := client.Codec()
		log.Printf("Client %s using %s encoding (index %d)", client.ID, codec.Name(), codec.Index())
	},
	nil, nil,
)

ControlDecoder Optional Interface

How control messages (subscribe / join_room, etc.) data fields are parsed depends on whether the codec implements ControlDecoder:

type ControlDecoder interface {
	DecodeChannel(data []byte) string
	DecodeRoomID(data []byte) string
}
  • ProtobufCodec: Implements this interface, parsing with ChannelRequest / RoomRequest Protobuf structures.
  • Other Codecs: Control message data fields use JSON encoding internally (default path).

Custom Sub-Protocol Configuration

config := websocket.DefaultConfig
config.Subprotocols = []string{"json"}  // Allow JSON only
// Or
config.Subprotocols = []string{"json", "protobuf", "flatbuffers", "msgpack"}  // Default: all four

hub := websocket.NewHub(l, config)

Protobuf Message Format

The Protobuf schema for WebSocket messages is defined in proto/message.proto:

message WsMessage {
  string type      = 1;  // Message type
  string channel   = 2;  // Channel name
  bytes  data      = 3;  // Opaque payload
  int64  timestamp = 4;  // Server timestamp
  string client_id = 5;  // Client identifier
}

Control messages (subscribe / join_room, etc.) data fields use separate Protobuf structures:

message ChannelRequest { string channel = 1; }
message RoomRequest    { string room_id = 1; }

Security Layer (AES-256-GCM + HMAC-SHA256)

Configuration

config := websocket.DefaultConfig
config.Security = &websocket.SecurityConfig{
	AESKey:          myAES256Key,  // 32 bytes, nil = no encryption
	HMACKey:         myHMACKey,    // Any length, nil = no signing
	SignThenEncrypt: true,         // true (default): sign first then encrypt
}

hub := websocket.NewHub(l, config)

Security Pipeline

Messages are automatically processed through the security pipeline before/after codec serialization/deserialization:

Sign-then-Encrypt (default, SignThenEncrypt: true):

Outbound: codec.Marshal -> HMAC-Sign -> AES-Encrypt -> wire
Inbound:  wire -> AES-Decrypt -> HMAC-Verify -> codec.Unmarshal

Encrypt-then-Sign (SignThenEncrypt: false):

Outbound: codec.Marshal -> AES-Encrypt -> HMAC-Sign -> wire
Inbound:  wire -> HMAC-Verify -> AES-Decrypt -> codec.Unmarshal

AES-256-GCM Encryption

  • Uses Go standard library crypto/aes + crypto/cipher
  • Each encryption uses crypto/rand to generate a unique 12-byte nonce
  • Nonce is prefixed to ciphertext: [nonce(12) | ciphertext | GCM-tag(16)]
  • Key must be 32 bytes (AES-256)

HMAC-SHA256 Signing

  • Uses Go standard library crypto/hmac + crypto/sha256
  • 32-byte signature prefixed to original data: [HMAC(32) | data]
  • Verification uses hmac.Equal for constant-time comparison, preventing timing attacks

Per-client Key Override

When different keys are needed for different clients:

hub.SetCallbacks(
	func(client *websocket.Client) {
		// Set client-specific keys based on authentication results
		clientAESKey := deriveKeyForUser(client.ID)
		client.SetEncryptionKey(clientAESKey)

		clientHMACKey := deriveHMACKeyForUser(client.ID)
		client.SetHMACKey(clientHMACKey)
	},
	nil, nil,
)

If no per-client key is set, Hub-level SecurityConfig keys are used.

Standalone Encryption/Decryption Functions

Security functions can be used independently, not limited to WebSocket scenarios:

key := make([]byte, 32) // AES-256 key
plaintext := []byte("sensitive data")

// Encrypt
ciphertext, err := websocket.Encrypt(plaintext, key)

// Decrypt
decrypted, err := websocket.Decrypt(ciphertext, key)

// Sign
hmacKey := []byte("my-hmac-secret")
signed := websocket.Sign(plaintext, hmacKey)

// Verify
verified, err := websocket.Verify(signed, hmacKey)

permessage-deflate Compression

Configuration

config := websocket.DefaultConfig
config.Compression = &websocket.CompressionConfig{
	Enabled: true,  // Default true
	Level:   6,     // flate compression level 1-9, 0=default
}

hub := websocket.NewHub(l, config)
  • Higher Level values mean better compression but more CPU usage
  • Set to 0 for Go's default compression level
  • Backward compatible with the original EnableCompression field: Compression overrides EnableCompression when non-nil

WSS/TLS Support

Within HypGo Framework

TLS is typically handled by the pkg/server layer (config.Server.TLS), and the WebSocket layer automatically uses wss://.

Standalone Mode

When the WebSocket Hub is used as a standalone server, quickly start via ListenAndServeTLS:

config := websocket.DefaultConfig
config.TLS = &websocket.TLSConfig{
	CertFile: "/path/to/cert.pem",
	KeyFile:  "/path/to/key.pem",
}
// Or provide a pre-configured tls.Config
config.TLS = &websocket.TLSConfig{
	TLSConfig: myTLSConfig,
}

hub := websocket.NewHub(l, config)

handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	// ... handle upgrade
})

// Start WSS server
err := hub.ListenAndServeTLS(":8443", handler)

Channel and Room Management

After connection establishment, you can actively change a client's group state for segmented broadcasting:

// Subscribe a client to a specific channel
client.Subscribe("news")

// Push structured Message to all subscribers in a channel (supports cross-protocol + security pipeline)
msg := websocket.AcquireMessage()
msg.Type = "message"
msg.Channel = "news"
msg.Data = []byte(`{"headline": "Breaking News!"}`)
hub.PublishToChannel("news", msg)
msg.Release()

// Or use the backward-compatible raw bytes API
hub.PublishToChannelRaw("news", []byte(`{"headline": "Breaking News!"}`))

// Join a Room for games or chat
client.JoinRoom("room_101")

// Global broadcast
hub.Broadcast([]byte(`{"event": "server_restart"}`))

// Send to a specific client (automatically uses that client's codec + security pipeline)
hub.SendToClient("client-123", msg)

Complete Configuration Example

config := websocket.Config{
	ReadBufferSize:    1024,
	WriteBufferSize:   1024,
	MaxMessageSize:    65536,
	WriteWait:         10 * time.Second,
	PongWait:          60 * time.Second,
	PingPeriod:        54 * time.Second,
	EnableCompression: true,
	Subprotocols:      []string{"json", "protobuf", "flatbuffers", "msgpack"},

	// Compression configuration (overrides EnableCompression)
	Compression: &websocket.CompressionConfig{
		Enabled: true,
		Level:   6,
	},

	// Security configuration
	Security: &websocket.SecurityConfig{
		AESKey:          aes256Key,   // 32 bytes
		HMACKey:         hmacSecret,
		SignThenEncrypt: true,
	},

	// TLS configuration (standalone mode)
	TLS: &websocket.TLSConfig{
		CertFile: "cert.pem",
		KeyFile:  "key.pem",
	},
}

hub := websocket.NewHub(l, config)

File Structure

pkg/websocket/
├── websocket.go              # Core: Client, Hub, Room, Config, readPump/writePump,
│                              #   TLS/Security/Compression integration, ListenAndServeTLS
├── codec.go                  # Codec/ControlDecoder interfaces, JSONCodec, ProtobufCodec,
│                              #   marshalForClients (map-based N-codec cache + security pipeline)
├── codec_flatbuffers.go      # FlatBuffersCodec (manual Builder API, zero-copy)
├── codec_msgpack.go          # MsgpackCodec (vmihailenco/msgpack/v5)
├── security.go               # AES-256-GCM encrypt/decrypt, HMAC-SHA256 sign/verify, security pipeline
├── proto/
│   ├── message.proto         # Protobuf schema definition
│   └── message.pb.go         # Protobuf encoding/decoding implementation
├── codec_test.go             # Cross-codec round-trip, marshalForClients, index uniqueness
├── codec_flatbuffers_test.go # FlatBuffers specific tests
├── codec_msgpack_test.go     # MessagePack specific tests
├── security_test.go          # AES/HMAC/pipeline/per-client key tests
├── websocket_test.go         # WebSocket core + sub-protocol negotiation tests
└── README.md

Dependencies

Package Purpose
github.com/gorilla/websocket WebSocket protocol implementation
github.com/google/flatbuffers/go FlatBuffers binary serialization
github.com/vmihailenco/msgpack/v5 MessagePack binary serialization
crypto/aes, crypto/cipher AES-256-GCM encryption (Go stdlib)
crypto/hmac, crypto/sha256 HMAC-SHA256 signing (Go stdlib)
crypto/tls TLS/WSS support (Go stdlib)

HypGo

繁體中文 | English


中文文件

設計文件

套件

AI 協作工具鏈

CLI 命令


English Docs

Design Docs

Packages

AI Collaboration Toolchain

CLI Commands

Clone this wiki locally