A small, gorilla-free WebSocket server for Go — clean API, RFC6455 handshake, frames, ping/pong keepalive, JSON helpers, and a simple room hub. Built to be extended (rate limiting, metrics, distributed hub) without leaking third-party types into your code.
Module:
github.com/aminofox/zsocket
- Minimal RFC6455 server-side implementation
- Clean API:
Accept(w, r, cfg) (*Conn, error)Conn.Read(ctx) (MessageType, []byte, error)Conn.Write(ctx, mt, data)Conn.ReadJSON/WriteJSONConn.Close(code, reason)Conn.Subprotocol(),Conn.RemoteAddr()
- Fragmentation (basic accumulation until
FIN) - Control frames: ping/pong/close
- Keepalive:
PingInterval+PongWait Hub: rooms & broadcast- Configurable read limit, deadlines, origin policy, subprotocols
go get github.com/aminofox/zsocketpackage main
import (
"context"
"log"
"net/http"
"github.com/aminofox/zsocket"
)
func main() {
cfg := zsocket.DefaultConfig()
hub := zsocket.NewHub()
http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
conn, err := zsocket.Accept(w, r, cfg)
if err != nil {
http.Error(w, "upgrade failed", http.StatusBadRequest)
return
}
hub.Join("lobby", conn)
go func() {
defer func() {
hub.LeaveAll(conn)
_ = conn.Close(zsocket.CloseNormalClosure, "bye")
}()
ctx := context.Background()
_ = conn.Write(ctx, zsocket.TextMessage, []byte("hello from zsocket"))
for {
mt, msg, err := conn.Read(ctx)
if err != nil {
log.Printf("read: %v", err)
return
}
_ = conn.Write(ctx, mt, msg) // echo
hub.Broadcast(ctx, "lobby", mt, msg)
}
}()
})
log.Fatal(http.ListenAndServe(":8080", nil))
}
cfg := zsocket.DefaultConfig()
cfg.AllowedOrigins = []string{"https://example.com"} // or nil to allow all
cfg.Subprotocols = []string{"chat", "json"}
cfg.ReadLimit = 16 << 20 // 16 MiB per message
cfg.PingInterval = 30 * time.Second
cfg.PongWait = 15 * time.Second
cfg.WriteTimeout = 10 * time.Second
type In struct { Cmd string `json:"cmd"`; Body any `json:"body"` }
var in In
if err := conn.ReadJSON(ctx, &in); err != nil { ... }
if err := conn.WriteJSON(ctx, map[string]any{"ok": true}); err != nil { ... }
hub.Join("room-42", conn)
hub.Broadcast(ctx, "room-42", zsocket.TextMessage, []byte("hi all"))
hub.Leave("room-42", conn)