Documentation
¶
Index ¶
- Variables
- func IsOrgConnectionLimitError(err error) bool
- type Connection
- func (c *Connection) Close(code int, reason string) error
- func (c *Connection) GetLastHeartbeat() time.Time
- func (c *Connection) GetStatus(heartbeatTimeout time.Duration) ConnectionStatus
- func (c *Connection) IsClosed() bool
- func (c *Connection) Send(message []byte) error
- func (c *Connection) UpdateHeartbeat()
- type ConnectionError
- type ConnectionStatus
- type DeliveryStats
- type Manager
- func (m *Manager) CanAcceptOrgConnection(orgID string) bool
- func (m *Manager) GetAllConnections() map[string][]*Connection
- func (m *Manager) GetConnectionCount() int
- func (m *Manager) GetConnections(gatewayID string) []*Connection
- func (m *Manager) GetOrgConnectionStats(orgID string) OrgConnectionStats
- func (m *Manager) IncrementDisconnections()
- func (m *Manager) IncrementFailedConnections()
- func (m *Manager) IncrementSuccessfulConnections()
- func (m *Manager) IncrementTotalEventsSent()
- func (m *Manager) Register(gatewayID string, transport Transport, authToken string, orgID string) (*Connection, error)
- func (m *Manager) SetConnectionHooks(onConnect, onDisconnect func(gatewayID string) error)
- func (m *Manager) Shutdown()
- func (m *Manager) Unregister(gatewayID, connectionID string)
- type ManagerConfig
- type OrgConnectionLimitError
- type OrgConnectionStats
- type Transport
- type WebSocketTransport
- func (t *WebSocketTransport) Close(code int, reason string) error
- func (t *WebSocketTransport) EnablePongHandler(handler func(string) error)
- func (t *WebSocketTransport) ReadMessage() (messageType int, payload []byte, err error)
- func (t *WebSocketTransport) Send(message []byte) error
- func (t *WebSocketTransport) SendPing() error
- func (t *WebSocketTransport) SetReadDeadline(deadline time.Time) error
- func (t *WebSocketTransport) SetWriteDeadline(deadline time.Time) error
Constants ¶
This section is empty.
Variables ¶
var ( // ErrConnectionClosed is returned when attempting to send on a closed connection ErrConnectionClosed = &ConnectionError{Message: "connection is closed"} )
Common connection errors
Functions ¶
func IsOrgConnectionLimitError ¶
IsOrgConnectionLimitError checks if an error is an OrgConnectionLimitError
Types ¶
type Connection ¶
type Connection struct {
// GatewayID identifies the gateway instance (UUID from gateway registration)
GatewayID string
// ConnectionID provides a unique identifier for this specific connection instance.
// Used to distinguish between multiple connections from the same gateway (clustering).
ConnectionID string
// OrganizationID identifies the organization that owns this gateway connection.
// Used for per-organization connection limit tracking and cleanup.
OrganizationID string
// ConnectedAt records when the connection was established
ConnectedAt time.Time
// LastHeartbeat records the timestamp of the most recent heartbeat (pong) received.
// Updated automatically by the pong handler to track connection liveness.
LastHeartbeat time.Time
// Transport provides the underlying protocol implementation for message delivery.
// Abstraction allows swapping WebSocket for other protocols without changing business logic.
Transport Transport
// AuthToken stores the API key used to authenticate this connection.
// Can be used for re-validation or audit logging.
AuthToken string
// DeliveryStats tracks event delivery statistics for this connection
DeliveryStats *DeliveryStats
// contains filtered or unexported fields
}
Connection represents an active gateway connection with metadata and lifecycle management. This wrapper decouples connection management logic from the underlying transport protocol.
Design rationale: By wrapping the Transport interface, we can:
- Track connection metadata (gateway ID, connection time, heartbeat status)
- Support multiple transport implementations (WebSocket, SSE, gRPC)
- Manage connection lifecycle (connect, heartbeat, disconnect) uniformly
func NewConnection ¶
func NewConnection(gatewayID, connectionID string, transport Transport, authToken string, orgID string) *Connection
NewConnection creates a new Connection wrapper with the provided parameters.
Parameters:
- gatewayID: UUID of the authenticated gateway
- connectionID: Unique identifier for this connection instance
- transport: Transport implementation (e.g., WebSocketTransport)
- authToken: API key used for authentication
- orgID: UUID of the organization that owns the gateway
Returns a fully initialized Connection ready for message delivery.
func (*Connection) Close ¶
func (c *Connection) Close(code int, reason string) error
Close terminates the connection gracefully with a close code and reason. This method is idempotent - calling it multiple times is safe.
Parameters:
- code: Protocol-specific close code (e.g., 1000 for normal WebSocket closure)
- reason: Human-readable reason for closure
Returns an error if the close operation fails (ignored if already closed).
func (*Connection) GetLastHeartbeat ¶
func (c *Connection) GetLastHeartbeat() time.Time
GetLastHeartbeat returns the timestamp of the most recent heartbeat. Used by the connection manager to detect stale/dead connections.
Thread-safe for concurrent access.
func (*Connection) GetStatus ¶
func (c *Connection) GetStatus(heartbeatTimeout time.Duration) ConnectionStatus
GetStatus returns the current connection status for monitoring and stats API.
Status values:
- "connected": Connection is active and receiving heartbeats
- "stale": No heartbeat received within timeout period (but not yet closed)
- "closed": Connection has been explicitly closed
Parameters:
- heartbeatTimeout: Duration after which a connection is considered stale
Thread-safe for concurrent access.
func (*Connection) IsClosed ¶
func (c *Connection) IsClosed() bool
IsClosed returns true if the connection has been explicitly closed. Thread-safe for concurrent access.
func (*Connection) Send ¶
func (c *Connection) Send(message []byte) error
Send delivers a message to the gateway through the underlying transport. This method is thread-safe and can be called concurrently.
Parameters:
- message: The message payload to send (typically JSON-encoded)
Returns an error if the send fails or the connection is already closed.
func (*Connection) UpdateHeartbeat ¶
func (c *Connection) UpdateHeartbeat()
UpdateHeartbeat records the current time as the last heartbeat timestamp. Called automatically by the pong handler when heartbeat frames are received.
Thread-safe for concurrent access.
type ConnectionError ¶
type ConnectionError struct {
Message string
}
ConnectionError represents connection-specific errors
func (*ConnectionError) Error ¶
func (e *ConnectionError) Error() string
type ConnectionStatus ¶
type ConnectionStatus struct {
GatewayID string `json:"gatewayId"`
ConnectionID string `json:"connectionId"`
ConnectedAt time.Time `json:"connectedAt"`
LastHeartbeat time.Time `json:"lastHeartbeat"`
Status string `json:"status"` // "connected", "stale", "closed"
}
ConnectionStatus represents the current state of a connection for monitoring.
type DeliveryStats ¶
type DeliveryStats struct {
// TotalEventsSent tracks the cumulative number of events sent to all gateways.
// Updated atomically using atomic.AddInt64() to ensure thread-safety.
TotalEventsSent int64
// FailedDeliveries tracks the cumulative number of event delivery failures.
// A failure occurs when:
// - Gateway is not connected (no active WebSocket connection)
// - Send operation fails (e.g., connection closed during send)
// - Payload exceeds maximum size limit
FailedDeliveries int64
// LastFailureTime records the timestamp of the most recent delivery failure.
// Not atomic - updated under lock or during single-threaded stats query.
LastFailureTime time.Time
// LastFailureReason contains a human-readable description of the most recent failure.
// Examples: "gateway not connected", "send timeout", "payload too large"
// Not atomic - updated under lock or during single-threaded stats query.
LastFailureReason string
}
DeliveryStats tracks event delivery statistics in memory using atomic operations. This structure provides operational visibility into event delivery success/failure rates without requiring database persistence.
Design rationale: Atomic counters enable lock-free concurrent updates from multiple goroutines handling event delivery. Stats reset on server restart, which is acceptable for operational monitoring (persistent metrics can be added via Prometheus later).
func (*DeliveryStats) GetFailedCount ¶
func (s *DeliveryStats) GetFailedCount() int64
GetFailedCount returns the current value of failed deliveries. Uses atomic load to ensure visibility of concurrent updates.
func (*DeliveryStats) GetSuccessRate ¶
func (s *DeliveryStats) GetSuccessRate() float64
GetSuccessRate calculates the percentage of successful event deliveries. Returns 100.0 if no events have been sent (avoiding division by zero).
func (*DeliveryStats) GetTotalSent ¶
func (s *DeliveryStats) GetTotalSent() int64
GetTotalSent returns the current value of total events sent. Uses atomic load to ensure visibility of concurrent updates.
func (*DeliveryStats) IncrementFailed ¶
func (s *DeliveryStats) IncrementFailed(reason string)
IncrementFailed atomically increments the failed deliveries counter and records the failure details.
Parameters:
- reason: Human-readable description of why the delivery failed
Note: LastFailureTime and LastFailureReason updates are not atomic. This is acceptable for monitoring purposes where exact synchronization is not critical.
func (*DeliveryStats) IncrementTotalSent ¶
func (s *DeliveryStats) IncrementTotalSent()
IncrementTotalSent atomically increments the total events sent counter. This method is thread-safe and can be called concurrently from multiple goroutines.
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager handles the lifecycle of gateway WebSocket connections. It maintains an in-memory registry of active connections, manages heartbeats, and handles graceful/ungraceful disconnections.
Design rationale: sync.Map provides thread-safe concurrent access optimized for read-heavy workloads (event delivery lookups). The registry maps gateway IDs to slices of connections to support multiple connections per gateway (clustering).
func NewManager ¶
func NewManager(config ManagerConfig, gatewayRepo repository.GatewayRepository, slogger *slog.Logger) *Manager
NewManager creates a new connection manager with the provided configuration
func (*Manager) CanAcceptOrgConnection ¶
CanAcceptOrgConnection checks if the organization can accept a new connection without actually adding it. Use this for pre-upgrade validation.
func (*Manager) GetAllConnections ¶
func (m *Manager) GetAllConnections() map[string][]*Connection
GetAllConnections returns all active connections across all gateways. Used by the stats API to provide operational visibility.
Returns a map of gatewayID -> []*Connection
func (*Manager) GetConnectionCount ¶
GetConnectionCount returns the total number of active connections
func (*Manager) GetConnections ¶
func (m *Manager) GetConnections(gatewayID string) []*Connection
GetConnections retrieves all connections for a specific gateway ID. Returns an empty slice if the gateway has no active connections.
Thread-safe for concurrent access.
func (*Manager) GetOrgConnectionStats ¶
func (m *Manager) GetOrgConnectionStats(orgID string) OrgConnectionStats
GetOrgConnectionStats returns connection statistics for a specific organization
func (*Manager) IncrementDisconnections ¶
func (m *Manager) IncrementDisconnections()
func (*Manager) IncrementFailedConnections ¶
func (m *Manager) IncrementFailedConnections()
func (*Manager) IncrementSuccessfulConnections ¶
func (m *Manager) IncrementSuccessfulConnections()
func (*Manager) IncrementTotalEventsSent ¶
func (m *Manager) IncrementTotalEventsSent()
func (*Manager) Register ¶
func (m *Manager) Register(gatewayID string, transport Transport, authToken string, orgID string) (*Connection, error)
Register adds a new connection to the registry and starts heartbeat monitoring. Returns an error if the maximum connection limit is reached.
Parameters:
- gatewayID: UUID of the authenticated gateway
- transport: Transport implementation for message delivery
- authToken: API key used for authentication
- orgID: UUID of the organization that owns the gateway
Returns the Connection instance and any error encountered.
Design decision: Support multiple connections per gateway ID by storing connections in a slice. This enables gateway clustering where multiple instances share the same gateway identity.
func (*Manager) SetConnectionHooks ¶
SetConnectionHooks registers optional callbacks invoked on every gateway connect and disconnect. onConnect is called after a connection is stored; onDisconnect after it is removed. Callbacks are invoked synchronously and must not acquire the manager's locks. This method must be called only during initialization, before the gateway begins accepting connections. It is not safe to call concurrently with Register or Unregister.
func (*Manager) Shutdown ¶
func (m *Manager) Shutdown()
Shutdown gracefully closes all connections and stops heartbeat monitoring. Waits for all connection handler goroutines to exit before returning.
This method should be called during server shutdown to cleanly terminate all gateway connections with a normal closure code.
func (*Manager) Unregister ¶
Unregister removes a connection from the registry and closes it gracefully. This method is idempotent - calling it multiple times is safe.
Parameters:
- gatewayID: UUID of the gateway
- connectionID: Unique identifier of the connection to remove
type ManagerConfig ¶
type ManagerConfig struct {
MaxConnections int // Maximum concurrent connections (default 1000)
HeartbeatInterval time.Duration // Ping interval (default 20s)
HeartbeatTimeout time.Duration // Pong timeout (default 30s)
MaxConnectionsPerOrg int // Maximum connections per organization (default 3)
MetricsLogEnabled bool // Enable periodic metrics logging (default true)
MetricsLogInterval time.Duration // Interval between metrics log entries (default 10s)
}
ManagerConfig contains configuration parameters for the connection manager
func DefaultManagerConfig ¶
func DefaultManagerConfig() ManagerConfig
DefaultManagerConfig returns sensible default configuration values
type OrgConnectionLimitError ¶
OrgConnectionLimitError is returned when an organization has reached its connection limit
func (*OrgConnectionLimitError) Error ¶
func (e *OrgConnectionLimitError) Error() string
type OrgConnectionStats ¶
type Transport ¶
type Transport interface {
// Send delivers a message to the connected client.
// Returns an error if the send operation fails (e.g., connection closed, timeout).
//
// Parameters:
// - message: The message payload to send (typically JSON-encoded)
//
// The implementation should handle protocol-specific framing and encoding.
Send(message []byte) error
// Close terminates the transport connection gracefully.
// The implementation should send appropriate close frames/messages as per
// the protocol specification (e.g., WebSocket close frame with code 1000).
//
// Parameters:
// - code: Protocol-specific close code (e.g., WebSocket close codes)
// - reason: Human-readable reason for connection closure
Close(code int, reason string) error
// SetReadDeadline sets the deadline for reading from the transport.
// Used for implementing heartbeat/keepalive timeout detection.
//
// Parameters:
// - deadline: Absolute time when read operations should timeout
//
// A zero time value disables the deadline.
SetReadDeadline(deadline time.Time) error
// SetWriteDeadline sets the deadline for writing to the transport.
// Used to prevent indefinite blocking on send operations.
//
// Parameters:
// - deadline: Absolute time when write operations should timeout
//
// A zero time value disables the deadline.
SetWriteDeadline(deadline time.Time) error
// EnablePongHandler configures automatic handling of pong frames for heartbeat.
// For WebSocket, this sets up the pong handler. Other transports may implement
// equivalent keepalive mechanisms.
//
// Parameters:
// - handler: Callback function invoked when a pong frame is received
//
// This is called to reset read deadlines and maintain connection liveness.
EnablePongHandler(handler func(string) error)
// SendPing sends a ping frame to test connection liveness.
// Returns an error if the ping cannot be sent.
//
// For protocols without built-in ping/pong, implementations may use
// application-level heartbeat messages.
SendPing() error
}
Transport defines an abstraction layer for protocol-independent message delivery. This interface allows the system to switch between different transport mechanisms (WebSocket, Server-Sent Events, gRPC, etc.) without modifying business logic.
Design rationale: The transport abstraction isolates protocol-specific code from event routing and connection management logic, enabling future protocol changes without rewriting the core system.
func NewWebSocketTransport ¶
NewWebSocketTransport creates a new WebSocket transport wrapper.
Parameters:
- conn: The established gorilla/websocket connection
Returns a Transport implementation backed by WebSocket.
type WebSocketTransport ¶
type WebSocketTransport struct {
// contains filtered or unexported fields
}
WebSocketTransport implements the Transport interface using gorilla/websocket. This provides the concrete WebSocket protocol implementation while isolating WebSocket-specific code from business logic.
Design rationale: By implementing the Transport interface, we can swap WebSocket for other protocols (SSE, gRPC) without changing the Connection or Manager code.
func (*WebSocketTransport) Close ¶
func (t *WebSocketTransport) Close(code int, reason string) error
Close terminates the WebSocket connection with a close frame.
Parameters:
- code: WebSocket close code (e.g., 1000 for normal closure)
- reason: Human-readable close reason
Returns an error if sending the close frame fails.
func (*WebSocketTransport) EnablePongHandler ¶
func (t *WebSocketTransport) EnablePongHandler(handler func(string) error)
EnablePongHandler configures the automatic pong frame handler. Called when a pong frame is received in response to a ping.
Parameters:
- handler: Callback invoked when pong frame arrives
func (*WebSocketTransport) ReadMessage ¶
func (t *WebSocketTransport) ReadMessage() (messageType int, payload []byte, err error)
ReadMessage reads the next message from the WebSocket connection. This is used by connection handlers to detect disconnections and handle incoming messages.
Returns:
- messageType: The type of message (Text, Binary, Close, Ping, Pong)
- payload: The message data
- error: Any error encountered during read
func (*WebSocketTransport) Send ¶
func (t *WebSocketTransport) Send(message []byte) error
Send delivers a message to the WebSocket client as a text frame.
Parameters:
- message: The message payload (typically JSON-encoded)
Returns an error if the write fails or the connection is closed.
func (*WebSocketTransport) SendPing ¶
func (t *WebSocketTransport) SendPing() error
SendPing sends a WebSocket ping frame to test connection liveness. The client should respond with a pong frame.
Returns an error if the ping cannot be sent.
func (*WebSocketTransport) SetReadDeadline ¶
func (t *WebSocketTransport) SetReadDeadline(deadline time.Time) error
SetReadDeadline sets the deadline for read operations. Used to detect heartbeat timeouts.
Parameters:
- deadline: Absolute time when reads should timeout (zero disables)
func (*WebSocketTransport) SetWriteDeadline ¶
func (t *WebSocketTransport) SetWriteDeadline(deadline time.Time) error
SetWriteDeadline sets the deadline for write operations. Prevents indefinite blocking on slow clients.
Parameters:
- deadline: Absolute time when writes should timeout (zero disables)