zsocket

package module
v1.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 7, 2026 License: MIT Imports: 21 Imported by: 0

README

zsocket

Go Reference Go Report Card

zsocket is a lightweight, zero-dependency, high-performance WebSocket & Event Engine library for Go. It features full RFC 6455 and RFC 7692 (permessage-deflate) compliance, an optimized low-level WebSocket engine, and a Socket.IO-style event-driven messaging module with Acknowledgements (ACK) and Room support.

Module: github.com/aminofox/zsocket


Key Features

  • 🚀 Dual Engine Architecture:
    • Core WebSocket: Low-level, high-throughput RFC 6455 engine with custom dialing, hijacking, streaming readers/writers, and JSON helpers.
    • Socket.IO Layer (zsocket/socketio): High-level event-driven protocol with On(), Emit(), EmitWithAck(), namespaces, and room broadcasting.
  • ⚡ High Performance & Zero-Alloc Optimizations:
    • Fast 64-bit word-at-a-time (SIMD-style) XOR payload unmasking.
    • Reader/Writer buffer pooling via sync.Pool.
    • Non-blocking room mutexes to eliminate broadcast lock contention.
  • 🛡️ Security & Hardened RFC Compliance:
    • Strict UTF-8 validation for text frames and close reasons (RFC 6455 §8.1).
    • Protection against 64-bit frame extended length MSB exploits (DoS prevention).
    • Cumulative multi-fragment ReadLimit enforcement to prevent memory exhaustion.
    • Proper HTTP error status responses (405, 400, 403) prior to connection hijacking.
  • 🗜️ RFC 7692 Deflate Compression: Full browser-compatible permessage-deflate with \x00\x00\xff\xff suffix stripping and pooled flate compressor instances.
  • 🔌 Rich Ecosystem: Includes built-in middleware for Authentication, Rate Limiting, Metrics, and a Pub/Sub adapter for Redis horizontal scaling.

Installation

go get github.com/aminofox/zsocket

Quick Start: Socket.IO Event Engine (zsocket/socketio)

Build event-driven real-time applications effortlessly with the Node.js Socket.IO-like API:

Server Example
package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/aminofox/zsocket"
	"github.com/aminofox/zsocket/socketio"
)

func main() {
	server := socketio.NewServer(zsocket.DefaultConfig())

	server.OnConnection(func(s *socketio.Socket) {
		fmt.Println("Client connected:", s.ID())

		// Join a room
		s.Join("lobby")

		// Register event handler with automatic ACK reply
		s.On("chat_message", func(data []byte) (any, error) {
			log.Printf("Received message: %s", string(data))

			// Broadcast to everyone in "lobby" except current client
			s.BroadcastTo("lobby").Emit("new_message", string(data))

			// Return response to trigger client ACK callback
			return map[string]string{"status": "delivered"}, nil
		})
	})

	http.Handle("/socket.io/", server)
	log.Println("Socket.IO server listening on :8080...")
	log.Fatal(http.ListenAndServe(":8080", nil))
}
Client Example
package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"github.com/aminofox/zsocket/socketio"
)

func main() {
	client, err := socketio.DialSocket("ws://localhost:8080/socket.io/", nil)
	if err != nil {
		log.Fatalf("Failed to connect: %v", err)
	}
	defer client.Close()

	// Listen for events
	client.On("new_message", func(data []byte) (any, error) {
		fmt.Println("Broadcast message:", string(data))
		return nil, nil
	})

	// Emit event and wait for ACK response
	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()

	ackResp, err := client.EmitWithAck(ctx, "chat_message", "Hello Socket.IO World!")
	if err != nil {
		log.Fatalf("ACK failed: %v", err)
	}
	fmt.Println("Server ACK:", string(ackResp))
}

Quick Start: Raw WebSocket Engine (zsocket)

If you prefer standard low-level WebSocket frames:

HTTP Upgrade & Echo Server
package main

import (
	"context"
	"log"
	"net/http"

	"github.com/aminofox/zsocket"
)

func main() {
	cfg := zsocket.DefaultConfig()
	upgrader := zsocket.Upgrader{Config: cfg}

	http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
		conn, err := upgrader.Upgrade(w, r, nil)
		if err != nil {
			log.Printf("Upgrade error: %v", err)
			return
		}
		defer conn.Close(zsocket.CloseNormalClosure, "server shutdown")

		ctx := context.Background()
		for {
			mt, msg, err := conn.Read(ctx)
			if err != nil {
				log.Printf("Read error: %v", err)
				return
			}
			// Echo message back
			if err := conn.Write(ctx, mt, msg); err != nil {
				return
			}
		}
	})

	log.Fatal(http.ListenAndServe(":8080", nil))
}

Configuration & Security Options

Config allows fine-tuning security boundaries and performance parameters:

cfg := zsocket.DefaultConfig()

// Security & Handshake
cfg.HandshakeTimeout = 10 * time.Second
cfg.ReadLimit = 16 << 20 // 16 MiB max message size
cfg.CheckOrigin = zsocket.IsSameOrigin // Same-Origin enforcement

// Keepalive & Deadlines
cfg.PingInterval = 30 * time.Second
cfg.PongWait = 15 * time.Second
cfg.WriteTimeout = 10 * time.Second

// Compression (RFC 7692)
cfg.EnableCompression = true
cfg.CompressionLevel = flate.BestSpeed
Origin Checking
// Custom Origin validation
cfg.CheckOrigin = func(r *http.Request) bool {
    origin := r.Header.Get("Origin")
    return origin == "https://mydomain.com"
}

Middleware & Scaling

1. Built-in Middleware (zsocket/middleware)
  • Authentication: middleware.Auth(...)
  • Rate Limiting: middleware.NewRateLimiter(...)
  • Metrics: Track connection counters and message volume.
2. Distributed Hub for Horizontal Scaling (zsocket/hub)

To scale WebSocket rooms horizontally across multiple server instances, use hub.RedisHub:

import "github.com/aminofox/zsocket/hub"

// Wrap pub/sub client to synchronize room broadcasts across nodes
redisHub := hub.NewRedisHub(pubsubClient)

License

Distributed under the MIT License.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrBadHandshake = errors.New("zsocket: bad handshake")
	ErrCloseSent    = errors.New("zsocket: close sent")
	ErrReadLimit    = errors.New("zsocket: read limit exceeded")
)
View Source
var (
	ErrBadRequest          = errors.New("zsocket: bad websocket upgrade request")
	ErrHandshakeFailed     = errors.New("zsocket: handshake failed")
	ErrOriginNotAllowed    = errors.New("zsocket: origin not allowed")
	ErrSubprotocolRejected = errors.New("zsocket: subprotocol rejected")
	ErrClosed              = errors.New("zsocket: connection closed")
	ErrTimeout             = errors.New("zsocket: timeout")
)
View Source
var DefaultDialer = &Dialer{
	Proxy:            http.ProxyFromEnvironment,
	HandshakeTimeout: 45 * time.Second,
	ReadBufferSize:   4096,
	WriteBufferSize:  4096,
}

DefaultDialer is the default WebSocket dialer.

View Source
var DefaultUpgrader = Upgrader{Config: DefaultConfig()}

DefaultUpgrader uses DefaultConfig values.

Functions

func FormatCloseMessage added in v1.0.1

func FormatCloseMessage(code CloseCode, text string) []byte

FormatCloseMessage formats close payload bytes from code and text.

func IsCloseError added in v1.0.1

func IsCloseError(err error, codes ...CloseCode) bool

IsCloseError checks whether err is a CloseError with one of the given codes.

func IsSameOrigin added in v1.0.1

func IsSameOrigin(r *http.Request) bool

IsSameOrigin returns true if request Origin header matches request Host header.

func IsUnexpectedCloseError added in v1.0.1

func IsUnexpectedCloseError(err error, expectedCodes ...CloseCode) bool

IsUnexpectedCloseError reports whether err is a CloseError that is not expected.

func IsWebSocketUpgrade added in v1.0.1

func IsWebSocketUpgrade(r *http.Request) bool

IsWebSocketUpgrade reports whether an HTTP request asks for websocket upgrade.

func JoinMessages added in v1.0.1

func JoinMessages(c *Conn, term string) io.Reader

JoinMessages returns a reader that joins text or binary messages with term.

func Subprotocols added in v1.0.1

func Subprotocols(r *http.Request) []string

Subprotocols returns the client requested subprotocol list.

Types

type BufferPool added in v1.0.1

type BufferPool interface {
	Get() any
	Put(any)
}

BufferPool is a generic reusable buffer pool.

func NewBufferPool added in v1.0.1

func NewBufferPool(size int) BufferPool

NewBufferPool creates a sync.Pool-backed byte buffer pool.

type CloseCode

type CloseCode uint16

CloseCode follows RFC 6455 close codes (select common ones here).

const (
	CloseNormalClosure           CloseCode = 1000
	CloseGoingAway               CloseCode = 1001
	CloseProtocolError           CloseCode = 1002
	CloseUnsupportedData         CloseCode = 1003
	CloseNoStatusRcvd            CloseCode = 1005 // reserved - do not send
	CloseNoStatusReceived        CloseCode = CloseNoStatusRcvd
	CloseAbnormalClosure         CloseCode = 1006 // reserved - do not send
	CloseInvalidFramePayloadData CloseCode = 1007
	ClosePolicyViolation         CloseCode = 1008
	CloseMessageTooBig           CloseCode = 1009
	CloseMandatoryExtension      CloseCode = 1010
	CloseInternalServerErr       CloseCode = 1011
	CloseTLSHandshake            CloseCode = 1015
)

type CloseError

type CloseError struct {
	Code   CloseCode
	Text   string
	Reason string
}

CloseError is returned when a close frame is received or needs to be sent.

func (CloseError) Error

func (e CloseError) Error() string

type CloseHandler added in v1.0.1

type CloseHandler func(code CloseCode, text string) error

CloseHandler handles incoming close control frames.

type Compressor added in v1.0.1

type Compressor interface {
	Compress(data []byte) ([]byte, error)
	Decompress(data []byte) ([]byte, error)
}

Compressor describes per-message compression behavior.

type Config

type Config struct {
	// AllowedOrigins is used to check request Origin. Empty means "allow all".
	AllowedOrigins []string

	// Subprotocols contains allowed subprotocol names.
	Subprotocols []string

	// HandshakeTimeout controls the HTTP upgrade deadline.
	HandshakeTimeout time.Duration

	// ReadLimit caps the maximum message payload (bytes). 0 means no limit.
	ReadLimit int64

	// ReadBufferSize / WriteBufferSize are used for buffered I/O.
	ReadBufferSize  int
	WriteBufferSize int

	// PingInterval sets how often to send pings. 0 disables keepalive pings.
	PingInterval time.Duration

	// PongWait is how long to wait for a pong after a ping. Must be > 0 if PingInterval > 0.
	PongWait time.Duration

	// WriteTimeout caps write operations (frames & control frames).
	WriteTimeout time.Duration

	// CheckOrigin allows custom origin validation. If set, overrides AllowedOrigins.
	CheckOrigin func(r *http.Request) bool

	// Logger is optional. If nil, logging is no-op.
	Logger Logger

	// EnableCompression enables per-message deflate negotiation.
	EnableCompression bool

	// CompressionLevel controls flate level when compression is enabled.
	CompressionLevel int

	// WriteBufferPool optionally provides reusable write buffers.
	WriteBufferPool BufferPool
}

Config controls WebSocket server behavior during handshake and runtime.

func DefaultConfig

func DefaultConfig() Config

DefaultConfig returns a practical default config for servers.

type Conn

type Conn struct {
	// contains filtered or unexported fields
}

Conn is a server-side WebSocket connection managed by zsocket. It exposes Read/Write methods, JSON helpers, and graceful Close. Concurrency: one reader goroutine internally; writes are serialized via writeMu.

NOTE: This MVP supports basic fragmentation (accumulate until FIN). Extensions are not implemented.

func Accept

func Accept(w http.ResponseWriter, r *http.Request, cfg Config) (*Conn, error)

Accept upgrades an HTTP request to a WebSocket connection and returns a *Conn. It does a minimal RFC6455 handshake with origin & subprotocol checks, then hijacks the underlying TCP connection for frame I/O.

func Dial added in v1.0.1

func Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Response, error)

Dial creates a client WebSocket connection.

func (*Conn) Close

func (c *Conn) Close(code CloseCode, reason string) error

Close sends a close frame (best-effort) and closes the connection.

func (*Conn) CloseHandler added in v1.0.1

func (c *Conn) CloseHandler() CloseHandler

CloseHandler returns the current close handler.

func (*Conn) EnableSendQueue added in v1.0.1

func (c *Conn) EnableSendQueue(size int)

EnableSendQueue configures async write queue with backpressure.

func (*Conn) EnableWriteCompression added in v1.0.1

func (c *Conn) EnableWriteCompression(enable bool)

EnableWriteCompression enables or disables write-side compression.

func (*Conn) LocalAddr added in v1.0.1

func (c *Conn) LocalAddr() net.Addr

LocalAddr returns the local network address.

func (*Conn) NetConn added in v1.0.1

func (c *Conn) NetConn() net.Conn

NetConn returns the underlying net.Conn.

func (*Conn) NextReader added in v1.0.1

func (c *Conn) NextReader() (MessageType, io.Reader, error)

NextReader returns the next complete message payload as an io.Reader.

func (*Conn) NextWriter added in v1.0.1

func (c *Conn) NextWriter(mt MessageType) (io.WriteCloser, error)

NextWriter returns a writer for the next message.

func (*Conn) PingHandler added in v1.0.1

func (c *Conn) PingHandler() PingHandler

PingHandler returns the current ping handler.

func (*Conn) PongHandler added in v1.0.1

func (c *Conn) PongHandler() PongHandler

PongHandler returns the current pong handler.

func (*Conn) Read

func (c *Conn) Read(ctx context.Context) (MessageType, []byte, error)

Read reads the next complete message, returning its type and payload. It accumulates fragments until FIN=1. Control frames are handled internally.

func (*Conn) ReadJSON

func (c *Conn) ReadJSON(ctx context.Context, v any) error

ReadJSON reads next message and unmarshals JSON into v. It accepts both text and binary frames. If binary, it still tries to decode JSON.

func (*Conn) ReadMessage added in v1.0.1

func (c *Conn) ReadMessage() (MessageType, []byte, error)

ReadMessage reads next data message.

func (*Conn) RemoteAddr

func (c *Conn) RemoteAddr() net.Addr

RemoteAddr returns the peer network address.

func (*Conn) Send added in v1.0.1

func (c *Conn) Send(ctx context.Context, mt MessageType, data []byte) error

Send enqueues a message if queue enabled, otherwise writes directly.

func (*Conn) SetCloseHandler added in v1.0.1

func (c *Conn) SetCloseHandler(h CloseHandler)

SetCloseHandler sets a custom close handler. Nil resets the default behavior.

func (*Conn) SetCompressionLevel added in v1.0.1

func (c *Conn) SetCompressionLevel(level int) error

SetCompressionLevel changes the compression level for future messages.

func (*Conn) SetPingHandler added in v1.0.1

func (c *Conn) SetPingHandler(h PingHandler)

SetPingHandler sets a custom ping handler. Nil resets the default behavior.

func (*Conn) SetPongHandler added in v1.0.1

func (c *Conn) SetPongHandler(h PongHandler)

SetPongHandler sets a custom pong handler. Nil resets the default behavior.

func (*Conn) SetReadDeadline added in v1.0.1

func (c *Conn) SetReadDeadline(t time.Time) error

SetReadDeadline sets the deadline for future reads.

func (*Conn) SetReadLimit added in v1.0.1

func (c *Conn) SetReadLimit(limit int64)

SetReadLimit sets the maximum message payload size in bytes. 0 means unlimited.

func (*Conn) SetWriteDeadline added in v1.0.1

func (c *Conn) SetWriteDeadline(t time.Time) error

SetWriteDeadline sets the deadline for future writes.

func (*Conn) Subprotocol

func (c *Conn) Subprotocol() string

Subprotocol returns the negotiated subprotocol, if any.

func (*Conn) UnderlyingConn added in v1.0.1

func (c *Conn) UnderlyingConn() net.Conn

UnderlyingConn is an alias for NetConn.

func (*Conn) Write

func (c *Conn) Write(ctx context.Context, mt MessageType, data []byte) error

Write sends a complete message (text or binary) as a single unfragmented frame.

func (*Conn) WriteControl

func (c *Conn) WriteControl(mt MessageType, payload []byte, deadline time.Time) error

WriteControl writes a control frame using message type and deadline.

func (*Conn) WriteJSON

func (c *Conn) WriteJSON(ctx context.Context, v any) error

WriteJSON marshals v to JSON and writes as a text message.

func (*Conn) WriteMessage added in v1.0.1

func (c *Conn) WriteMessage(mt MessageType, data []byte) error

WriteMessage writes one data message.

func (*Conn) WritePreparedMessage added in v1.0.1

func (c *Conn) WritePreparedMessage(pm *PreparedMessage) error

WritePreparedMessage writes a prebuilt message efficiently.

type Deflate added in v1.0.1

type Deflate struct {
	// contains filtered or unexported fields
}

Deflate implements RFC7692 per-message deflate.

func NewDeflate added in v1.0.1

func NewDeflate(level int) (*Deflate, error)

NewDeflate creates a deflate compressor with the given level.

func (*Deflate) Compress added in v1.0.1

func (d *Deflate) Compress(data []byte) ([]byte, error)

func (*Deflate) Decompress added in v1.0.1

func (d *Deflate) Decompress(data []byte) ([]byte, error)

type Dialer added in v1.0.1

type Dialer struct {
	NetDial           func(network, addr string) (net.Conn, error)
	NetDialContext    func(ctx context.Context, network, addr string) (net.Conn, error)
	NetDialTLSContext func(ctx context.Context, network, addr string) (net.Conn, error)
	Proxy             func(*http.Request) (*url.URL, error)
	TLSClientConfig   *tls.Config
	HandshakeTimeout  time.Duration
	Subprotocols      []string
	ReadBufferSize    int
	WriteBufferSize   int
	EnableCompression bool
	Jar               http.CookieJar
}

Dialer configures WebSocket client connections.

func (*Dialer) Dial added in v1.0.1

func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Response, error)

Dial connects to a WebSocket server.

func (*Dialer) DialContext added in v1.0.1

func (d *Dialer) DialContext(ctx context.Context, urlStr string, requestHeader http.Header) (*Conn, *http.Response, error)

DialContext connects to a WebSocket server with context.

type Hub

type Hub struct {
	// contains filtered or unexported fields
}

Hub manages subscriptions (rooms) and broadcasts messages to members. It is safe for concurrent use.

NOTE: Basic single-process hub. For horizontal scale, back it with Redis pub/sub or another broker.

func NewHub

func NewHub() *Hub

NewHub creates a new Hub.

func (*Hub) Broadcast

func (h *Hub) Broadcast(ctx context.Context, room string, mt MessageType, msg []byte)

Broadcast sends msg to all connections in room.

func (*Hub) BroadcastNonBlocking added in v1.0.1

func (h *Hub) BroadcastNonBlocking(room string, mt MessageType, msg []byte)

BroadcastNonBlocking sends with short per-connection timeout.

func (*Hub) Join

func (h *Hub) Join(room string, c *Conn)

Join subscribes connection c to a room.

func (*Hub) Leave

func (h *Hub) Leave(room string, c *Conn)

Leave unsubscribes connection c from a room.

func (*Hub) LeaveAll

func (h *Hub) LeaveAll(c *Conn)

LeaveAll removes c from all rooms.

type Logger

type Logger interface {
	Debugf(format string, args ...any)
	Infof(format string, args ...any)
	Warnf(format string, args ...any)
	Errorf(format string, args ...any)
}

type MessageType

type MessageType byte

MessageType represents the WebSocket message type.

const (
	TextMessage   MessageType = 1
	BinaryMessage MessageType = 2
	CloseMessage  MessageType = 8
	PingMessage   MessageType = 9
	PongMessage   MessageType = 10
)

type NopLogger

type NopLogger struct{}

func (NopLogger) Debugf

func (NopLogger) Debugf(string, ...any)

func (NopLogger) Errorf

func (NopLogger) Errorf(string, ...any)

func (NopLogger) Infof

func (NopLogger) Infof(string, ...any)

func (NopLogger) Warnf

func (NopLogger) Warnf(string, ...any)

type PingHandler added in v1.0.1

type PingHandler func(appData string) error

PingHandler handles incoming ping control frames.

type PongHandler added in v1.0.1

type PongHandler func(appData string) error

PongHandler handles incoming pong control frames.

type PreparedMessage added in v1.0.1

type PreparedMessage struct {
	// contains filtered or unexported fields
}

PreparedMessage stores message data for repeated writes.

func NewPreparedMessage added in v1.0.1

func NewPreparedMessage(mt MessageType, data []byte) (*PreparedMessage, error)

NewPreparedMessage creates a prepared message for repeated sending.

type Upgrader added in v1.0.1

type Upgrader struct {
	Config Config
}

Upgrader upgrades HTTP requests to websocket connections.

func (Upgrader) Upgrade added in v1.0.1

func (u Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*Conn, error)

Upgrade upgrades request with optional additional response headers.

Directories

Path Synopsis
examples
autobahn command
basic_server command
socketio_server command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL