-
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) |
設計文件
套件
- 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