-
Notifications
You must be signed in to change notification settings - Fork 0
websocket_en.md
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.
-
Four-Protocol Serialization: Clients select serialization format during WebSocket handshake via
Sec-WebSocket-Protocolsub-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
ListenAndServeTLSfor quick secure WebSocket server startup. -
Zero-Configuration Upgrade: Built-in Gorilla WebSocket
Upgraderwrapper with standard configuration for cross-origin checks and security settings. -
Object Pooling:
Client,Room,Message, buffers, and broadcast client slices are all managed viasync.Pool. Clientmetadatamaps are pre-allocated in the pool, andreset()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.
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...
}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)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,
)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/RoomRequestProtobuf structures. -
Other Codecs: Control message
datafields use JSON encoding internally (default path).
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)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; }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)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
- Uses Go standard library
crypto/aes+crypto/cipher - Each encryption uses
crypto/randto generate a unique 12-byte nonce - Nonce is prefixed to ciphertext:
[nonce(12) | ciphertext | GCM-tag(16)] - Key must be 32 bytes (AES-256)
- Uses Go standard library
crypto/hmac+crypto/sha256 - 32-byte signature prefixed to original data:
[HMAC(32) | data] - Verification uses
hmac.Equalfor constant-time comparison, preventing timing attacks
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.
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)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
Levelvalues mean better compression but more CPU usage - Set to 0 for Go's default compression level
- Backward compatible with the original
EnableCompressionfield:CompressionoverridesEnableCompressionwhen non-nil
TLS is typically handled by the pkg/server layer (config.Server.TLS), and the WebSocket layer automatically uses wss://.
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)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)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)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
| 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) |
由 台灣卯小月 用 ❤️ 製作 · MIT License And Wiki is written by Claude
設計文件
套件
- config — 設定
- context — 請求上下文
- router — 路由器
- server — 伺服器
- middleware — 中介層
- websocket — WebSocket
- hidb — 資料庫 ORM
- hidb/cassandra — Cassandra
- logger — 日誌
- json — JSON 處理
- grpc — gRPC
AI 協作工具鏈
- schema — Schema-first 路由
- manifest — 專案 Manifest
- contract — Contract Testing
- errors — Typed Error Catalog
- migrate — Migration Diff
- scaffold — 智慧 Scaffold
- airules — AI Rules
CLI 命令
- hyp 總覽
- hyp new
- hyp api
- hyp run
- hyp restart
- hyp generate
- hyp migrate
- hyp context
- hyp ai-rules
- hyp chkcomment
- hyp impact
- hyp docker
- hyp health
- hyp version
- hyp difflog
Design Docs
Packages
- config — Configuration
- context — Request Context
- router — Router
- server — Server
- middleware — Middleware
- websocket — WebSocket
- hidb — Database ORM
- hidb/cassandra - Cassandra 5.0
- logger — Logger
- json — JSON
- grpc — gRPC
AI Collaboration Toolchain
- schema — Schema-first Routing
- manifest — Project Manifest
- contract — Contract Testing
- errors — Typed Error Catalog
- migrate — Migration Diff
- scaffold — Smart Scaffold
- airules — AI Rules
CLI Commands