http

package
v0.0.6 Latest Latest
Warning

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

Go to latest
Published: Mar 17, 2026 License: Apache-2.0 Imports: 21 Imported by: 6

Documentation

Overview

Package http provides utilities for HTTP request handling and production-grade WebSocket connections.

This package contains two main areas of functionality:

  1. HTTP utilities for simplified request/response handling
  2. WebSocket abstractions built on Gorilla WebSocket for real-time applications

WebSocket Framework

The WebSocket framework provides a production-ready abstraction over Gorilla WebSocket with automatic connection management, heartbeat detection, and lifecycle hooks.

## Key Features

  • Production-grade connection management with automatic heartbeat detection
  • Lifecycle hooks for connection start, close, timeout, and error handling
  • Thread-safe message broadcasting to multiple clients
  • Automatic ping-pong mechanism to prevent connection timeouts
  • Type-safe message handling with Go generics
  • Pluggable Codec system for flexible message encoding (JSON, Protobuf)
  • Generic BaseConn[I, O] for typed input/output messages
  • Built-in JSON message support with JSONConn
  • Configurable timeouts and intervals for different deployment scenarios

## Quick Start

The simplest way to create a WebSocket endpoint is using the built-in JSONConn:

type EchoConn struct {
	gohttp.JSONConn
}

func (e *EchoConn) HandleMessage(msg any) error {
	// Echo the message back to the client
	e.Writer.Send(conc.Message[any]{Value: msg})
	return nil
}

type EchoHandler struct{}

func (h *EchoHandler) Validate(w http.ResponseWriter, r *http.Request) (*EchoConn, bool) {
	return &EchoConn{}, true // Accept all connections
}

// Register with HTTP router
router.HandleFunc("/echo", gohttp.WSServe(&EchoHandler{}, nil))

## Architecture

The framework uses three main interfaces:

### WSConn[I any] Represents a WebSocket connection that can handle typed messages:

type WSConn[I any] interface {
	BiDirStreamConn[I]
	ReadMessage(w *websocket.Conn) (I, error)
	OnStart(conn *websocket.Conn) error
}

### WSHandler[I any, S WSConn[I]] Validates HTTP requests and creates WebSocket connections:

type WSHandler[I any, S WSConn[I]] interface {
	Validate(w http.ResponseWriter, r *http.Request) (S, bool)
}

### BiDirStreamConn[I any] Provides lifecycle and message handling methods:

type BiDirStreamConn[I any] interface {
	SendPing() error
	Name() string
	ConnId() string
	HandleMessage(msg I) error
	OnError(err error) error
	OnClose()
	OnTimeout() bool
}

## Advanced Usage

### Multi-user Chat Server

type ChatServer struct {
	clients map[string]*ChatConn
	rooms   map[string]*Room
	mu      sync.RWMutex
}

type ChatConn struct {
	gohttp.JSONConn
	server   *ChatServer
	username string
	roomName string
}

func (c *ChatConn) OnStart(conn *websocket.Conn) error {
	if err := c.JSONConn.OnStart(conn); err != nil {
		return err
	}

	// Register with server
	c.server.mu.Lock()
	c.server.clients[c.ConnId()] = c
	c.server.mu.Unlock()

	return nil
}

func (c *ChatConn) HandleMessage(msg any) error {
	msgMap := msg.(map[string]any)

	switch msgMap["type"].(string) {
	case "chat":
		// Broadcast to all clients in room
		c.server.broadcastToRoom(c.roomName, msgMap)
	case "join_room":
		// Switch rooms
		c.joinRoom(msgMap["room"].(string))
	}

	return nil
}

### Authentication and Authorization

type SecureConn struct {
	gohttp.JSONConn
	userID string
	roles  []string
}

func (s *SecureConn) HandleMessage(msg any) error {
	msgMap := msg.(map[string]any)
	messageType := msgMap["type"].(string)

	if !s.hasPermission(messageType) {
		errorMsg := map[string]any{
			"type":  "error",
			"error": "Insufficient permissions",
		}
		s.Writer.Send(conc.Message[any]{Value: errorMsg})
		return nil
	}

	// Process authorized message
	return s.processMessage(msgMap)
}

type AuthHandler struct{}

func (h *AuthHandler) Validate(w http.ResponseWriter, r *http.Request) (*SecureConn, bool) {
	token := r.Header.Get("Authorization")

	// Validate JWT token
	claims, err := validateJWT(token)
	if err != nil {
		http.Error(w, "Invalid token", http.StatusUnauthorized)
		return nil, false
	}

	return &SecureConn{
		userID: claims["userID"].(string),
		roles:  claims["roles"].([]string),
	}, true
}

### Custom Ping-Pong with Latency Tracking

type MonitoredConn struct {
	gohttp.JSONConn
	lastPingTime time.Time
	pingCount    int64
}

func (m *MonitoredConn) SendPing() error {
	m.pingCount++
	m.lastPingTime = time.Now()

	pingMsg := map[string]any{
		"type":      "ping",
		"pingId":    m.pingCount,
		"timestamp": m.lastPingTime.Unix(),
	}

	m.Writer.Send(conc.Message[any]{Value: pingMsg})
	return nil
}

func (m *MonitoredConn) HandleMessage(msg any) error {
	msgMap := msg.(map[string]any)

	if msgMap["type"].(string) == "pong" {
		latency := time.Since(m.lastPingTime)
		log.Printf("Ping-pong latency: %v", latency)
	}

	return nil
}

## Configuration

### Production Configuration

config := &gohttp.WSConnConfig{
	BiDirStreamConfig: &gohttp.BiDirStreamConfig{
		PingPeriod: time.Second * 30,  // Send ping every 30 seconds
		PongPeriod: time.Second * 300, // Timeout after 5 minutes
	},
	Upgrader: websocket.Upgrader{
		ReadBufferSize:  4096,
		WriteBufferSize: 4096,
		CheckOrigin: func(r *http.Request) bool {
			// Implement proper origin checking
			return isValidOrigin(r.Header.Get("Origin"))
		},
	},
}

router.HandleFunc("/ws", gohttp.WSServe(handler, config))

### Development Configuration

config := gohttp.DefaultWSConnConfig()
config.PingPeriod = time.Second * 10  // Faster pings for development
config.PongPeriod = time.Second * 30  // Shorter timeout

## Frontend Integration

The framework is designed to work seamlessly with JavaScript WebSocket clients:

class WebSocketClient {
	constructor(url) {
		this.url = url;
		this.ws = null;
		this.pingInterval = null;
	}

	connect() {
		this.ws = new WebSocket(this.url);

		this.ws.onopen = () => {
			this.startPingInterval();
		};

		this.ws.onmessage = (event) => {
			const message = JSON.parse(event.data);
			this.handleMessage(message);
		};
	}

	handleMessage(message) {
		switch (message.type) {
			case 'ping':
				// Respond to server ping
				this.send({type: 'pong', pingId: message.pingId});
				break;
			default:
				this.onMessage(message);
		}
	}

	startPingInterval() {
		this.pingInterval = setInterval(() => {
			if (this.ws?.readyState === WebSocket.OPEN) {
				this.send({type: 'ping', timestamp: Date.now()});
			}
		}, 25000);
	}
}

## Error Handling and Resilience

The framework provides robust error handling:

type ResilientConn struct {
	gohttp.JSONConn
	errorCount int
	maxErrors  int
}

func (r *ResilientConn) OnError(err error) error {
	r.errorCount++
	log.Printf("WebSocket error #%d: %v", r.errorCount, err)

	if r.errorCount > r.maxErrors {
		return err // Close connection after too many errors
	}

	return nil // Continue with connection
}

func (r *ResilientConn) OnTimeout() bool {
	log.Printf("Connection timeout for %s", r.ConnId())
	return true // Close the connection
}

## Best Practices

  • Always call parent OnStart/OnClose when embedding JSONConn
  • Implement proper authentication in the Validate method
  • Use connection limits to prevent resource exhaustion
  • Handle errors gracefully without crashing the server
  • Clean up resources properly in OnClose methods
  • Use mutexes for thread-safe operations on shared state
  • Monitor connection health with metrics

## Common Patterns

### Hub Pattern for Broadcasting

type Hub struct {
	clients    map[*Client]bool
	broadcast  chan []byte
	register   chan *Client
	unregister chan *Client
}

func (h *Hub) run() {
	for {
		select {
		case client := <-h.register:
			h.clients[client] = true
		case client := <-h.unregister:
			delete(h.clients, client)
		case message := <-h.broadcast:
			for client := range h.clients {
				client.send <- message
			}
		}
	}
}

### Room-based Messaging

type Room struct {
	name    string
	clients map[string]*Client
	mu      sync.RWMutex
}

func (r *Room) Broadcast(message any, excludeId string) {
	r.mu.RLock()
	defer r.mu.RUnlock()

	for id, client := range r.clients {
		if id != excludeId {
			client.Send(message)
		}
	}
}

## Testing

The package includes comprehensive test utilities for WebSocket testing:

// Create test server
server := httptest.NewServer(router)
defer server.Close()

// Create WebSocket client
wsURL := "ws" + strings.TrimPrefix(server.URL, "http") + "/endpoint"
conn, err := createTestClient(t, wsURL, nil)
if err != nil {
	t.Fatalf("Failed to connect: %v", err)
}
defer conn.Close()

See the comprehensive test suite in ws2_test.go for complete examples of testing WebSocket applications including authentication, load testing, and error scenarios.

HTTP Utilities

The package also provides utilities for HTTP request/response handling:

  • JsonToQueryString: Convert maps to URL query strings
  • SendJsonResponse: Send JSON responses with proper error handling
  • ErrorToHttpCode: Convert Go errors to appropriate HTTP status codes
  • WSConnWriteMessage/WSConnWriteError: WebSocket message writing utilities
  • NormalizeWsUrl: Convert HTTP URLs to WebSocket URLs

Example HTTP utility usage:

func handleAPI(w http.ResponseWriter, r *http.Request) {
	data, err := processRequest(r)
	gohttp.SendJsonResponse(w, data, err)
}

This comprehensive framework enables building production-ready real-time applications with WebSocket communication, from simple echo servers to complex multi-user systems with authentication, rooms, and advanced connection management.

Example

Example demonstrating complete WebSocket workflow

// Create router and server
router := mux.NewRouter()

// Chat server setup
chatServer := &ChatServer{
	clients: make(map[string]*ChatConn),
	rooms:   make(map[string]*Room),
}

// Add handlers
router.HandleFunc("/echo", WSServe(&EchoHandler{}, nil))
router.HandleFunc("/chat", WSServe(&ChatHandler{server: chatServer}, nil))
router.HandleFunc("/secure", WSServe(&AuthHandler{}, nil))

// Custom configuration for production
config := &WSConnConfig{
	BiDirStreamConfig: &BiDirStreamConfig{
		PingPeriod: time.Second * 30,
		PongPeriod: time.Second * 300,
	},
	Upgrader: websocket.Upgrader{
		ReadBufferSize:  4096,
		WriteBufferSize: 4096,
		CheckOrigin: func(r *http.Request) bool {
			// Add proper origin checking in production
			return true
		},
	},
}

router.HandleFunc("/production", WSServe(&EchoHandler{}, config))

// In a real application, you would start the server:
// log.Fatal(http.ListenAndServe(":8080", router))

fmt.Println("WebSocket server configured with multiple endpoints:")
fmt.Println("- /echo: Simple echo server")
fmt.Println("- /chat: Multi-user chat with rooms")
fmt.Println("- /secure: Authenticated connections")
fmt.Println("- /production: Production-ready config")
Output:
WebSocket server configured with multiple endpoints:
- /echo: Simple echo server
- /chat: Multi-user chat with rooms
- /secure: Authenticated connections
- /production: Production-ready config

Index

Examples

Constants

This section is empty.

Variables

View Source
var DefaultHttpClient *http.Client
View Source
var HighQPSHttpClient *http.Client
View Source
var LowQPSHttpClient *http.Client
View Source
var MediumQPSHttpClient *http.Client

Functions

func CORS deprecated

func CORS(next http.Handler) http.Handler

CORS is a convenience wrapper for local development that allows all origins. For production use, prefer middleware.CORS with an OriginChecker.

Deprecated: Use middleware.CORS(nil) for allow-all, or middleware.CORS(middleware.NewOriginChecker(origins)) for production.

func Call

func Call(req *http.Request, client *http.Client) (response any, err error)

Makes a http with the tiven request and the http client. This is a wrapper over the standard library caller that creates a Client (if not provided), performs the request reads the entire body adn optionally converts the payload to an appropriate type based on the response' Content-Type header (for now only application/json is supported.

func ErrorToHttpCode

func ErrorToHttpCode(err error) int

ErrorToHttpCode converts a Go error to an appropriate HTTP status code. If err is nil, returns http.StatusOK (200). If err contains a gRPC status, maps it to the corresponding HTTP code:

  • codes.PermissionDenied → 403 Forbidden
  • codes.NotFound → 404 Not Found
  • codes.AlreadyExists → 409 Conflict
  • codes.InvalidArgument → 400 Bad Request
  • Other errors → 500 Internal Server Error

func HTTPErrorCode

func HTTPErrorCode(err error) int

func JsonGet

func JsonGet(url string, onReq func(req *http.Request)) (any, *http.Response, error)

A simple wrapper for performing JSON Get requests. The url is the full url once all query params have been added. The onReq callback allows customization of the http requests before it is sent.

func JsonToQueryString

func JsonToQueryString(json map[string]any) string

JsonToQueryString converts a map to a URL query string. Keys are sorted alphabetically for deterministic output. Values are converted to strings using fmt.Sprintf("%v", value).

Example:

params := map[string]any{"name": "John", "age": 30}
qs := JsonToQueryString(params) // "age=30&name=John"
Example
input := map[string]any{"a": 1, "b": 2}
queryStr := JsonToQueryString(input)
fmt.Println(queryStr)
Output:
a=1&b=2

func MakeUrl

func MakeUrl(host, path string, args string) (url string)

Creates a URL on a host, path and with optional query parameters

func NewBytesRequest

func NewBytesRequest(method string, endpoint string, body []byte) (req *http.Request, err error)

Wraps the NewRequest helper to create request to set the body from a byte array.

func NewJsonRequest

func NewJsonRequest(method string, endpoint string, body map[string]any) (req *http.Request, err error)

Wraps the NewRequest helper to create a request with the payload marshalled as JSON.

func NewRequest

func NewRequest(method string, endpoint string, bodyReader io.Reader) (req *http.Request, err error)

Creates a new http request with the given method, endpoint and a bodyready that provides the content the request body.

func NormalizeWsUrl

func NormalizeWsUrl(httpOrWsUrl string) string

NormalizeWsUrl converts an HTTP(S) URL to its WebSocket equivalent. It performs the following transformations:

  • Removes trailing slashes
  • Converts "http:" to "ws:"
  • Converts "https:" to "wss:"

URLs that are already WebSocket URLs (ws: or wss:) are returned unchanged after removing any trailing slash.

Example:

NormalizeWsUrl("https://example.com/ws/") // "wss://example.com/ws"
Example
fmt.Println(NormalizeWsUrl("http://google.com"))
fmt.Println(NormalizeWsUrl("https://github.com"))
Output:
ws://google.com
wss://github.com

func SendJsonResponse

func SendJsonResponse(writer http.ResponseWriter, resp any, err error)

SendJsonResponse writes a JSON response to the http.ResponseWriter. If err is nil, resp is marshaled to JSON and written with status 200 OK. If err is non-nil, an appropriate HTTP error code is set based on the gRPC status code (if present), and an error object is returned in the response body.

The function handles gRPC status errors by extracting the code and message, and maps them to appropriate HTTP status codes via ErrorToHttpCode.

func WSConnJSONReaderWriter

func WSConnJSONReaderWriter(conn *websocket.Conn) (reader *conc.Reader[gut.StrMap], writer *conc.Writer[conc.Message[gut.StrMap]])

WSConnJSONReaderWriter creates concurrent reader and writer for JSON messages over a WebSocket connection. The reader decodes incoming JSON messages into StrMap (map[string]any), and the writer sends outgoing StrMap messages as JSON.

The reader handles connection close errors gracefully by converting them to net.ErrClosed. The writer handles io.EOF as a normal stream end and uses WSConnWriteError for error messages.

This is useful for creating bidirectional JSON message streams over WebSocket.

func WSConnWriteError

func WSConnWriteError(wsConn *websocket.Conn, err error) error

WSConnWriteError writes an error message to a WebSocket connection. If err is nil or io.EOF, no message is sent and nil is returned. For gRPC status errors, the error code is extracted and sent as JSON. The message is sent as a text frame containing JSON: {"error": <code>}

func WSConnWriteMessage

func WSConnWriteMessage(wsConn *websocket.Conn, msg any) error

WSConnWriteMessage writes a JSON message to a WebSocket connection. The message is marshaled to JSON and sent as a text frame. Returns any error from marshaling or writing.

func WSHandleConn

func WSHandleConn[I any, S WSConn[I]](conn *websocket.Conn, ctx S, config *WSConnConfig)

WSHandleConn manages the lifecycle of an established WebSocket connection. It handles:

  • Periodic ping messages for connection health checks
  • Timeout detection when no data is received within PongPeriod
  • Message reading and dispatching to ctx.HandleMessage()
  • Error handling via ctx.OnError()
  • Clean shutdown via ctx.OnClose()

This function is called automatically by WSServe, but can also be used directly when you have an established WebSocket connection from another source.

The function blocks until the connection is closed or an unrecoverable error occurs.

Example
{
}

func WSServe

func WSServe[I any, S WSConn[I]](handler WSHandler[I, S], config *WSConnConfig) http.HandlerFunc

WSServe creates an http.HandlerFunc that upgrades HTTP requests to WebSocket connections and manages their lifecycle. This is the primary entry point for creating WebSocket endpoints.

The handler validates incoming requests and creates connection instances. The config controls upgrade behavior and timing; if nil, DefaultWSConnConfig is used.

Example:

router.HandleFunc("/ws", gohttp.WSServe(&MyHandler{}, nil))

The lifecycle is:

  1. handler.Validate() is called to check the request
  2. If valid, the connection is upgraded to WebSocket
  3. conn.OnStart() is called to initialize the connection
  4. Messages are read and passed to conn.HandleMessage()
  5. On close, conn.OnClose() is called for cleanup
Example
r := mux.NewRouter()
r.HandleFunc("/publish", func(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Publishing Custom Message")
})

r.HandleFunc("/subscribe", WSServe(&JSONHandler{}, nil))
srv := http.Server{Handler: r}
log.Fatal(srv.ListenAndServe())

Types

type BaseConn added in v0.0.4

type BaseConn[I any, O any] struct {
	// Codec handles message encoding/decoding.
	// Must be set before the connection is used.
	Codec Codec[I, O]

	// Writer is the output channel for sending messages.
	// Handles all outgoing messages: data, pings, and errors.
	// Initialized in OnStart.
	Writer *conc.Writer[OutgoingMessage[O]]

	// NameStr is an optional human-readable name for this connection.
	NameStr string

	// ConnIdStr is a unique identifier for this connection.
	// Auto-generated if not set.
	ConnIdStr string

	// PingId tracks the current ping sequence number.
	PingId int64
	// contains filtered or unexported fields
}

BaseConn is a generic WebSocket connection that separates transport from encoding. It uses a Codec to handle message serialization/deserialization.

Type parameters:

  • I: Input message type (received from client)
  • O: Output message type (sent to client)

Usage:

type MyConn struct {
    gohttp.BaseConn[MyInput, MyOutput]
}

func (c *MyConn) HandleMessage(msg MyInput) error {
    // msg is already typed!
    return nil
}

func (*BaseConn[I, O]) ConnId added in v0.0.4

func (b *BaseConn[I, O]) ConnId() string

ConnId returns the connection ID, generating one if not set.

func (*BaseConn[I, O]) DebugInfo added in v0.0.4

func (b *BaseConn[I, O]) DebugInfo() any

DebugInfo returns debug information about the connection.

func (*BaseConn[I, O]) HandleMessage added in v0.0.4

func (b *BaseConn[I, O]) HandleMessage(msg I) error

HandleMessage processes an incoming message. Default implementation just logs; override in embedding struct.

func (*BaseConn[I, O]) InputChan added in v0.0.4

func (b *BaseConn[I, O]) InputChan() chan<- OutgoingMessage[O]

InputChan returns the Writer's input channel for use with FanOut.

func (*BaseConn[I, O]) Name added in v0.0.4

func (b *BaseConn[I, O]) Name() string

Name returns the connection name.

func (*BaseConn[I, O]) OnClose added in v0.0.4

func (b *BaseConn[I, O]) OnClose()

OnClose cleans up when the connection closes.

func (*BaseConn[I, O]) OnError added in v0.0.4

func (b *BaseConn[I, O]) OnError(err error) error

OnError handles connection errors. Return nil to suppress the error and continue, or return the error to close.

func (*BaseConn[I, O]) OnStart added in v0.0.4

func (b *BaseConn[I, O]) OnStart(conn *websocket.Conn) error

OnStart initializes the connection after WebSocket upgrade. Creates the Writer with codec-aware encoding.

func (*BaseConn[I, O]) OnTimeout added in v0.0.4

func (b *BaseConn[I, O]) OnTimeout() bool

OnTimeout handles read timeout. Return true to close the connection, false to keep it alive.

func (*BaseConn[I, O]) ReadMessage added in v0.0.4

func (b *BaseConn[I, O]) ReadMessage(conn *websocket.Conn) (I, error)

ReadMessage reads and decodes the next message from the WebSocket connection. Uses the configured Codec to decode the raw bytes.

func (*BaseConn[I, O]) SendError added in v0.0.4

func (b *BaseConn[I, O]) SendError(err error)

SendError sends an error to the client.

func (*BaseConn[I, O]) SendOutput added in v0.0.4

func (b *BaseConn[I, O]) SendOutput(msg O)

SendOutput sends a typed output message to the client. This is a convenience method that wraps the Writer.Send call.

func (*BaseConn[I, O]) SendPing added in v0.0.4

func (b *BaseConn[I, O]) SendPing() error

SendPing sends a ping message through the Writer. This ensures thread-safe writes by going through the serialized Writer.

type BaseHandler added in v0.0.4

type BaseHandler[I any, O any] struct {
	// Codec is used for all connections created by this handler.
	Codec Codec[I, O]

	// Name is the optional name for connections.
	Name string
}

BaseHandler is a simple handler that creates BaseConn instances. Useful for quick prototyping or when you don't need custom validation.

func (*BaseHandler[I, O]) Validate added in v0.0.4

func (h *BaseHandler[I, O]) Validate(w http.ResponseWriter, r *http.Request) (*BaseConn[I, O], bool)

Validate implements WSHandler. Creates a new BaseConn with the configured codec.

type BiDirStreamConfig

type BiDirStreamConfig struct {
	// PingPeriod specifies how often to send ping messages to the remote peer.
	// Pings are used as heartbeat messages to verify the connection is alive.
	// Default: 30 seconds.
	PingPeriod time.Duration

	// PongPeriod specifies the maximum time to wait for any data (ping, pong, or
	// regular messages) from the remote peer before considering the connection dead.
	// If no data is received within this duration, OnTimeout() is called.
	// Default: 300 seconds (5 minutes).
	PongPeriod time.Duration
}

BiDirStreamConfig provides configuration for bidirectional stream connections. It controls the timing of health checks and connection timeout detection.

func DefaultBiDirStreamConfig

func DefaultBiDirStreamConfig() *BiDirStreamConfig

DefaultBiDirStreamConfig returns a BiDirStreamConfig with sensible defaults:

  • PingPeriod: 30 seconds
  • PongPeriod: 300 seconds (5 minutes)

These defaults are suitable for most production deployments. For development or testing, you may want shorter periods for faster feedback.

type BiDirStreamConn

type BiDirStreamConn[I any] interface {
	// SendPing sends a heartbeat ping message to the remote peer.
	// Called periodically based on BiDirStreamConfig.PingPeriod.
	// The implementation should encode and send an appropriate ping message.
	SendPing() error

	// Name returns an optional human-readable name for this connection type.
	// Used for logging and debugging purposes.
	Name() string

	// ConnId returns a unique identifier for this specific connection instance.
	// Useful for tracking individual connections in logs and metrics.
	ConnId() string

	// HandleMessage processes an incoming message of type I.
	// Called for each message received from the remote peer.
	// Return an error to signal that the connection should be closed.
	HandleMessage(msg I) error

	// OnError is called when an error occurs during connection operation.
	// Return nil to suppress the error and continue the connection.
	// Return the error (or a different error) to close the connection.
	OnError(err error) error

	// OnClose is called when the connection is closing for any reason.
	// Use this hook to clean up resources (timers, goroutines, etc.)
	// and perform any final actions (logging, metrics, etc.).
	OnClose()

	// OnTimeout is called when no data has been received within the PongPeriod.
	// Return true to close the connection, false to continue waiting.
	// Typically returns true, but may return false for connections that
	// expect long periods of silence.
	OnTimeout() bool
}

BiDirStreamConn defines the lifecycle and message handling interface for bidirectional stream connections. Implementations handle messages of type I and manage connection state through lifecycle hooks.

The typical lifecycle is:

  1. Connection established, OnStart() called (not part of this interface)
  2. Messages received and processed via HandleMessage()
  3. Periodic SendPing() calls to maintain connection health
  4. On errors, OnError() is called to determine if connection should continue
  5. On timeout (no data received within PongPeriod), OnTimeout() is called
  6. Connection ends, OnClose() is called for cleanup

type BinaryProtoCodec added in v0.0.4

type BinaryProtoCodec[I proto.Message, O proto.Message] struct {
	// NewInput is an optional factory function to create new input instances.
	// When provided, this is used for optimal performance.
	NewInput func() I

	// Input is an exemplar instance used as fallback when NewInput is nil.
	Input I
}

BinaryProtoCodec handles encoding/decoding of protobuf messages using binary format. This provides maximum efficiency for high-throughput scenarios.

For best performance, provide NewInput factory function. If not provided, the codec falls back to reflection using the Input exemplar (slower).

func (*BinaryProtoCodec[I, O]) Decode added in v0.0.4

func (c *BinaryProtoCodec[I, O]) Decode(data []byte, msgType MessageType) (I, error)

Decode unmarshals binary protobuf data into a new message instance.

func (*BinaryProtoCodec[I, O]) Encode added in v0.0.4

func (c *BinaryProtoCodec[I, O]) Encode(msg O) ([]byte, MessageType, error)

Encode marshals a protobuf message to binary bytes.

type Codec added in v0.0.4

type Codec[I any, O any] interface {
	// Decode converts raw WebSocket data into a typed input message.
	// msgType indicates whether the data was received as text or binary.
	Decode(data []byte, msgType MessageType) (I, error)

	// Encode converts a typed output message to raw bytes for sending.
	// Returns the encoded bytes and the appropriate message type (text/binary).
	Encode(msg O) ([]byte, MessageType, error)
}

Codec handles encoding/decoding of messages over WebSocket. The type parameters I and O represent input (received) and output (sent) message types. Note: Pings are handled at the transport layer (BaseConn), not by codecs.

type HTTPError

type HTTPError struct {
	Code    int
	Message string
}

Representing HTTP specific errors

func (*HTTPError) Error

func (t *HTTPError) Error() string

type JSONCodec added in v0.0.4

type JSONCodec struct{}

JSONCodec handles encoding/decoding of arbitrary JSON messages. This is useful for dynamic message handling where the structure isn't known at compile time.

func (*JSONCodec) Decode added in v0.0.4

func (c *JSONCodec) Decode(data []byte, msgType MessageType) (any, error)

Decode unmarshals JSON data into an untyped any value.

func (*JSONCodec) Encode added in v0.0.4

func (c *JSONCodec) Encode(msg any) ([]byte, MessageType, error)

Encode marshals any value to JSON bytes.

type JSONConn

type JSONConn = BaseConn[any, any]

JSONConn is an alias for BaseConn with untyped JSON messages. This provides a simple way to handle dynamic JSON messages.

For typed messages, use BaseConn[YourInputType, YourOutputType] directly with an appropriate codec.

func NewJSONConn added in v0.0.4

func NewJSONConn() *JSONConn

NewJSONConn creates a new JSONConn with the default JSON codec.

type JSONHandler

type JSONHandler struct{}

JSONHandler is a simple handler that creates JSONConn instances. All connections are accepted (no validation).

func (*JSONHandler) Validate

func (j *JSONHandler) Validate(w http.ResponseWriter, r *http.Request) (*JSONConn, bool)

Validate implements WSHandler. Accepts all connections.

type MessageType added in v0.0.4

type MessageType int

MessageType represents the WebSocket frame type

const (
	// TextMessage denotes a text data message (UTF-8 encoded)
	TextMessage MessageType = websocket.TextMessage // 1

	// BinaryMessage denotes a binary data message
	BinaryMessage MessageType = websocket.BinaryMessage // 2
)

type OutgoingMessage added in v0.0.4

type OutgoingMessage[O any] struct {
	// Data is a regular output message (mutually exclusive with Ping/Error)
	Data *O

	// Ping is a heartbeat message (mutually exclusive with Data/Error)
	Ping *PingData

	// Error is an error message (mutually exclusive with Data/Ping)
	Error error
}

OutgoingMessage represents any message that can be sent over the WebSocket. This union type allows pings, errors, and data messages to all go through the same Writer, avoiding concurrent write issues.

type PingData added in v0.0.4

type PingData struct {
	PingId int64
	ConnId string
	Name   string
}

PingData contains ping message metadata.

type ProtoJSONCodec added in v0.0.4

type ProtoJSONCodec[I proto.Message, O proto.Message] struct {
	// NewInput is an optional factory function to create new input instances.
	// When provided, this is used for optimal performance.
	// Example: func() *pb.PlayerAction { return &pb.PlayerAction{} }
	NewInput func() I

	// Input is an exemplar instance used as fallback when NewInput is nil.
	// The codec uses reflection to create new instances of the same type.
	// Example: &pb.PlayerAction{}
	Input I

	// MarshalOptions configures protojson marshaling behavior.
	MarshalOptions protojson.MarshalOptions

	// UnmarshalOptions configures protojson unmarshaling behavior.
	UnmarshalOptions protojson.UnmarshalOptions
}

ProtoJSONCodec handles encoding/decoding of protobuf messages using JSON format. This provides human-readable wire format while maintaining proto type safety.

For best performance, provide NewInput factory function. If not provided, the codec falls back to reflection using the Input exemplar (slower).

func (*ProtoJSONCodec[I, O]) Decode added in v0.0.4

func (c *ProtoJSONCodec[I, O]) Decode(data []byte, msgType MessageType) (I, error)

Decode unmarshals JSON data into a new protobuf message instance.

func (*ProtoJSONCodec[I, O]) Encode added in v0.0.4

func (c *ProtoJSONCodec[I, O]) Encode(msg O) ([]byte, MessageType, error)

Encode marshals a protobuf message to JSON bytes.

type TypedJSONCodec added in v0.0.4

type TypedJSONCodec[I any, O any] struct{}

TypedJSONCodec handles encoding/decoding of strongly-typed JSON messages. Use this when you have known Go struct types for your messages.

func (*TypedJSONCodec[I, O]) Decode added in v0.0.4

func (c *TypedJSONCodec[I, O]) Decode(data []byte, msgType MessageType) (I, error)

Decode unmarshals JSON data into a typed value. Creates a new instance of I using Go's zero value mechanism.

func (*TypedJSONCodec[I, O]) Encode added in v0.0.4

func (c *TypedJSONCodec[I, O]) Encode(msg O) ([]byte, MessageType, error)

Encode marshals a typed value to JSON bytes.

type URLWaiter

type URLWaiter struct {
	Method             string
	Url                string
	Headers            map[string]string
	Payload            map[string]any
	DelayBetweenChecks time.Duration

	// Func versions of above so we can do something dynamcially on each iteration
	RequestFunc  func(iter int, prevError error) (*http.Request, error)
	ValidateFunc func(req *http.Request, resp *http.Response) error
}

A simple utility that waits for a url to return a successful response before proceeding. This can be used for things like waiting for a database or another service to become available before performing other activities.

func (*URLWaiter) Run

func (u *URLWaiter) Run() (success bool, iter int, err error)

Runs the "waiter".

Returns a tuple of:

	success - whether the waited-upon eventually became active.
	iter    - How many iterations where run before a success/failure
 	err     - Error encountered if the "wait" was a failure

type WSConn

type WSConn[I any] interface {
	BiDirStreamConn[I]

	// ReadMessage reads and decodes the next message from the WebSocket connection.
	// This is called in a loop by WSHandleConn to process incoming messages.
	// Returns the decoded message or an error (including io.EOF on close).
	ReadMessage(w *websocket.Conn) (I, error)

	// OnStart is called when the WebSocket connection is established.
	// Use this to initialize the connection (e.g., set up writers, start goroutines).
	// Return an error to reject and close the connection.
	OnStart(conn *websocket.Conn) error
}

WSConn represents a bidirectional WebSocket connection that can handle typed messages of type I. It extends BiDirStreamConn with WebSocket-specific functionality for reading messages and connection initialization.

Implementations typically embed BaseConn[I, O] and override HandleMessage. The type parameter I represents the input message type received from clients.

type WSConnConfig

type WSConnConfig struct {
	*BiDirStreamConfig
	// Upgrader handles the HTTP to WebSocket protocol upgrade.
	// Configure ReadBufferSize, WriteBufferSize, and CheckOrigin as needed.
	Upgrader websocket.Upgrader
}

WSConnConfig combines BiDirStreamConfig with WebSocket-specific settings. It controls connection upgrade behavior and lifecycle timing.

func DefaultWSConnConfig

func DefaultWSConnConfig() *WSConnConfig

DefaultWSConnConfig returns a WSConnConfig with sensible defaults:

  • ReadBufferSize: 1024 bytes
  • WriteBufferSize: 1024 bytes
  • CheckOrigin: allows all origins (configure for production!)
  • PingPeriod: 30 seconds
  • PongPeriod: 300 seconds (5 minutes)

type WSHandler

type WSHandler[I any, S WSConn[I]] interface {
	// Validate checks if the HTTP request should be upgraded to a WebSocket.
	// Return (connection, true) to proceed with the upgrade.
	// Return (nil, false) to reject (the handler should write the error response).
	Validate(w http.ResponseWriter, r *http.Request) (S, bool)
}

WSHandler validates HTTP requests and creates WebSocket connections. It acts as a factory for WSConn instances, typically performing authentication and authorization before allowing the upgrade.

Type parameters:

  • I: The input message type that the connection will handle
  • S: The specific WSConn implementation type (must implement WSConn[I])

Jump to

Keyboard shortcuts

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