Documentation
¶
Overview ¶
Package http provides utilities for HTTP request handling and production-grade WebSocket connections.
This package contains two main areas of functionality:
- HTTP utilities for simplified request/response handling
- 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 ¶
- Variables
- func CORS(next http.Handler) http.Handler
- func Call(req *http.Request, client *http.Client) (response any, err error)
- func ErrorToHttpCode(err error) int
- func HTTPErrorCode(err error) int
- func JsonGet(url string, onReq func(req *http.Request)) (any, *http.Response, error)
- func JsonToQueryString(json map[string]any) string
- func MakeUrl(host, path string, args string) (url string)
- func NewBytesRequest(method string, endpoint string, body []byte) (req *http.Request, err error)
- func NewJsonRequest(method string, endpoint string, body map[string]any) (req *http.Request, err error)
- func NewRequest(method string, endpoint string, bodyReader io.Reader) (req *http.Request, err error)
- func NormalizeWsUrl(httpOrWsUrl string) string
- func SendJsonResponse(writer http.ResponseWriter, resp any, err error)
- func WSConnJSONReaderWriter(conn *websocket.Conn) (reader *conc.Reader[gut.StrMap], writer *conc.Writer[conc.Message[gut.StrMap]])
- func WSConnWriteError(wsConn *websocket.Conn, err error) error
- func WSConnWriteMessage(wsConn *websocket.Conn, msg any) error
- func WSHandleConn[I any, S WSConn[I]](conn *websocket.Conn, ctx S, config *WSConnConfig)
- func WSServe[I any, S WSConn[I]](handler WSHandler[I, S], config *WSConnConfig) http.HandlerFunc
- type BaseConn
- func (b *BaseConn[I, O]) ConnId() string
- func (b *BaseConn[I, O]) DebugInfo() any
- func (b *BaseConn[I, O]) HandleMessage(msg I) error
- func (b *BaseConn[I, O]) InputChan() chan<- OutgoingMessage[O]
- func (b *BaseConn[I, O]) Name() string
- func (b *BaseConn[I, O]) OnClose()
- func (b *BaseConn[I, O]) OnError(err error) error
- func (b *BaseConn[I, O]) OnStart(conn *websocket.Conn) error
- func (b *BaseConn[I, O]) OnTimeout() bool
- func (b *BaseConn[I, O]) ReadMessage(conn *websocket.Conn) (I, error)
- func (b *BaseConn[I, O]) SendError(err error)
- func (b *BaseConn[I, O]) SendOutput(msg O)
- func (b *BaseConn[I, O]) SendPing() error
- type BaseHandler
- type BiDirStreamConfig
- type BiDirStreamConn
- type BinaryProtoCodec
- type Codec
- type HTTPError
- type JSONCodec
- type JSONConn
- type JSONHandler
- type MessageType
- type OutgoingMessage
- type PingData
- type ProtoJSONCodec
- type TypedJSONCodec
- type URLWaiter
- type WSConn
- type WSConnConfig
- type WSHandler
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var DefaultHttpClient *http.Client
var HighQPSHttpClient *http.Client
var LowQPSHttpClient *http.Client
var MediumQPSHttpClient *http.Client
Functions ¶
func Call ¶
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 ¶
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 JsonGet ¶
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 ¶
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 NewBytesRequest ¶
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 ¶
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 ¶
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 ¶
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:
- handler.Validate() is called to check the request
- If valid, the connection is upgraded to WebSocket
- conn.OnStart() is called to initialize the connection
- Messages are read and passed to conn.HandleMessage()
- 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
ConnId returns the connection ID, generating one if not set.
func (*BaseConn[I, O]) DebugInfo ¶ added in v0.0.4
DebugInfo returns debug information about the connection.
func (*BaseConn[I, O]) HandleMessage ¶ added in v0.0.4
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]) 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
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
OnStart initializes the connection after WebSocket upgrade. Creates the Writer with codec-aware encoding.
func (*BaseConn[I, O]) OnTimeout ¶ added in v0.0.4
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
ReadMessage reads and decodes the next message from the WebSocket connection. Uses the configured Codec to decode the raw bytes.
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.
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:
- Connection established, OnStart() called (not part of this interface)
- Messages received and processed via HandleMessage()
- Periodic SendPing() calls to maintain connection health
- On errors, OnError() is called to determine if connection should continue
- On timeout (no data received within PongPeriod), OnTimeout() is called
- 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 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.
type JSONConn ¶
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 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
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.
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])