zsocket

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Aug 23, 2025 License: MIT Imports: 16 Imported by: 0

README

zsocket

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

Features

  • 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/WriteJSON
    • Conn.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

Quick Start

go get github.com/aminofox/zsocket
package 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))
}

Config

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

JSON Helpers

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 { ... }

Rooms & Broadcast

hub.Join("room-42", conn)
hub.Broadcast(ctx, "room-42", zsocket.TextMessage, []byte("hi all"))
hub.Leave("room-42", conn)

Documentation

Index

Constants

This section is empty.

Variables

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")
)

Functions

This section is empty.

Types

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
	CloseAbnormalClosure         CloseCode = 1006 // reserved - do not send
	CloseInvalidFramePayloadData CloseCode = 1007
	ClosePolicyViolation         CloseCode = 1008
	CloseMessageTooBig           CloseCode = 1009
	CloseMandatoryExtension      CloseCode = 1010
	CloseInternalServerErr       CloseCode = 1011
)

type CloseError

type CloseError struct {
	Code   CloseCode
	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 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(origin string) bool

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

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 (*Conn) Close

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

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

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) RemoteAddr

func (c *Conn) RemoteAddr() string

RemoteAddr returns the peer address as string.

func (*Conn) Subprotocol

func (c *Conn) Subprotocol() string

Subprotocol returns the negotiated subprotocol, if any.

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(opcode byte, payload []byte) error

WriteControl writes a control frame (ping/pong/close). Best-effort utility.

func (*Conn) WriteJSON

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

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

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) 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
)

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)

Directories

Path Synopsis
examples
basic_server command

Jump to

Keyboard shortcuts

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