websocket

package module
v0.1.0-preview.1 Latest Latest
Warning

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

Go to latest
Published: Aug 6, 2026 License: Apache-2.0 Imports: 19 Imported by: 0

README

Spice WebSocket starter

github.com/spice-framework/starter-websocket is the independently versioned, opt-in RFC 6455 client and server integration for Spice applications. It wraps github.com/coder/websocket with fail-closed transport, authentication, origin, resource-limit, lifecycle, and diagnostic policy while leaving HTTP servers, TLS certificates, credentials, routing, contexts, and shutdown with the application.

Secure server boundary

NewHandler constructs an ordinary http.Handler; it never starts a listener, opens a connection, reads ambient configuration, or registers global state. TLS and authentication are required by default. Anonymous access and plaintext loopback development are separate, explicit choices.

handler, err := websocket.NewHandler(websocket.ServerConfig{
	Authenticate: func(ctx context.Context, request *http.Request) error {
		return authorizer.Check(ctx, request.Header.Get("Authorization"))
	},
	OriginPatterns:  []string{"app.example.com"},
	Subprotocols:    []string{"orders.v1"},
	MaxMessageBytes: 64 << 10,
	MaxConnections:  128,
}, serveSession)
if err != nil {
	return err
}
server.Handler = handler

The caller must serve this handler through a TLS-enabled http.Server. AllowInsecure accepts only loopback requests. AllowAnyOrigin requires a non-nil authenticator. Authorization failures return a generic response and never expose the authenticator's error.

Explicit client connection

Dial is the only outbound network operation. It requires either an explicit authorization value or AllowAnonymous, requires wss outside loopback, clones caller TLS and header state defensively, rejects redirects, and applies a bounded handshake timeout.

connection, response, cleanup, err := websocket.Dial(ctx, websocket.ClientConfig{
	URL:              "wss://events.example.com:443/orders",
	Authorization:    "Bearer " + token,
	Subprotocols:     []string{"orders.v1"},
	MaxMessageBytes:  64 << 10,
	HandshakeTimeout: 5 * time.Second,
}, observer)
if err != nil {
	return err
}
defer response.Body.Close()
defer cleanup(shutdownContext)

The application owns the returned connection and cleanup. Reads, writes, ping, dial, and close use caller contexts. Cleanup performs one graceful close and is idempotent; cancellation force-closes the socket. Observer receives only direction, negotiated subprotocol, outcome, and duration. It never receives URLs, headers, credentials, peer addresses, close reasons, or message payloads. Peer close errors retain the status code but redact the close reason.

Manifest, compatibility, and migration

Manifest declares the explicit NewHandler and Dial entrypoints. Importing the package alone has no runtime effect. Existing consumers migrate from github.com/spice-framework/spice/starter/websocket to github.com/spice-framework/starter-websocket; constructor semantics remain ordinary Go and require no runtime Spice compiler.

Development and verification require exactly Go 1.26.5. The machine-readable spice-compatibility.json records the exact minimum and current Spice core lines. The repository gate proves both lines, real local TLS behavior, race safety, an 85% coverage floor, security analysis, reproducible vendor contents, and offline builds.

make check
make compatibility
make release-parity
make verify
make verify-release

Release parity runs the exact spice-dev tool authorized by go.mod and the retained repository builder twice each, entirely from vendor with network and workspace resolution disabled. It requires byte-identical source archives, fully drains and bounds their gzip/tar streams, rejects hidden data and extra members, requires equivalent SBOM facts, verifies canonical checksum files, and forbids rehearsal signatures on Windows and Linux.

See docs/dependency-review.md and docs/support.md.

Releases

Each version tag is an ordinary Go module release. The repository also builds an exact-commit source archive, committed-graph SPDX 2.3 SBOM, SHA-256 checksums, and an Ed25519 signature/public key without an external release build system. Production mode requires a clean checkout, exact tag, and protected signing key; an explicit unsigned rehearsal is available for local proof. See docs/releasing.md for the artifact and trust contract. The retained repository builder and signed production workflow remain the release authority while the centrally rendered unsigned candidate is held to the dual-builder parity contract.

Documentation

Overview

Package websocket provides reviewed, bounded RFC 6455 server and client integration for Spice applications.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Manifest

func Manifest() spicestarter.Manifest

Manifest returns WebSocket starter compatibility and review metadata.

Types

type AuthenticateFunc

type AuthenticateFunc func(context.Context, *http.Request) error

AuthenticateFunc validates one HTTP upgrade request. Returning an error rejects the request with a generic response; the error text is never sent to the peer or an Observer.

type ClientConfig

type ClientConfig struct {
	URL              string
	Header           http.Header
	Subprotocols     []string
	TLSConfig        *tls.Config
	MaxMessageBytes  int64
	Compression      bool
	Authorization    string
	HandshakeTimeout time.Duration
	AllowInsecure    bool
	AllowAnonymous   bool
}

ClientConfig defines one explicit outbound WebSocket connection.

type Connection

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

Connection is one bounded, caller-owned WebSocket connection.

func Dial

func Dial(
	ctx context.Context,
	config ClientConfig,
	observers ...Observer,
) (*Connection, *http.Response, lifecycle.Cleanup, error)

Dial opens one explicit outbound WebSocket connection.

func (*Connection) Close

func (connection *Connection) Close(
	ctx context.Context,
	status StatusCode,
	reason string,
) error

Close performs one close handshake. Cancellation force-closes the socket and returns the caller's cause.

func (*Connection) Ping

func (connection *Connection) Ping(ctx context.Context) error

Ping verifies that the peer responds using the caller-owned context.

func (*Connection) Read

func (connection *Connection) Read(
	ctx context.Context,
) (MessageType, []byte, error)

Read reads one complete bounded message. Canceling the context closes the underlying connection, matching the native library's explicit contract.

func (*Connection) Subprotocol

func (connection *Connection) Subprotocol() string

Subprotocol returns the negotiated application protocol.

func (*Connection) Write

func (connection *Connection) Write(
	ctx context.Context,
	messageType MessageType,
	payload []byte,
) error

Write sends one complete bounded text or binary message.

type Direction

type Direction string

Direction identifies an inbound or outbound WebSocket session.

const (
	// DirectionClient identifies an outbound session.
	DirectionClient Direction = "client"
	// DirectionServer identifies an inbound session.
	DirectionServer Direction = "server"
)

type Handler

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

Handler is an instance-owned HTTP WebSocket upgrade boundary.

func NewHandler

func NewHandler(
	config ServerConfig,
	handle SessionHandler,
	observers ...Observer,
) (*Handler, error)

NewHandler constructs a WebSocket HTTP handler without starting a listener or goroutine.

func (*Handler) Active

func (handler *Handler) Active() int64

Active returns the number of currently accepted sessions.

func (*Handler) ServeHTTP

func (handler *Handler) ServeHTTP(
	writer http.ResponseWriter,
	request *http.Request,
)

ServeHTTP upgrades and owns one bounded session. TLS termination remains the caller-owned HTTP server's responsibility.

type Interaction

type Interaction struct {
	Direction   Direction
	Subprotocol string
}

Interaction contains payload-free session facts.

type MessageType

type MessageType int

MessageType identifies a text or binary WebSocket message.

const (
	// MessageText identifies a UTF-8 text message.
	MessageText MessageType = 1
	// MessageBinary identifies a binary message.
	MessageBinary MessageType = 2
)

type Observer

type Observer interface {
	BeginSession(context.Context, Interaction) func(Result)
}

Observer receives session begin/end facts without message payloads, headers, URLs, peer addresses, or close reasons.

type Outcome

type Outcome string

Outcome identifies a payload-free terminal session state.

const (
	// OutcomeNormal identifies an ordinary completed or peer-closed session.
	OutcomeNormal Outcome = "normal"
	// OutcomeCanceled identifies caller or lifecycle cancellation.
	OutcomeCanceled Outcome = "canceled"
	// OutcomeFailed identifies an application or protocol failure.
	OutcomeFailed Outcome = "failed"
)

type PeerCloseError

type PeerCloseError struct {
	Status StatusCode
}

PeerCloseError reports a peer's close status without retaining its potentially sensitive close reason.

func (*PeerCloseError) Error

func (closeError *PeerCloseError) Error() string

Error implements error.

type Result

type Result struct {
	Interaction Interaction
	Outcome     Outcome
	Duration    time.Duration
}

Result describes one completed session.

type ServerConfig

type ServerConfig struct {
	OriginPatterns  []string
	Subprotocols    []string
	MaxMessageBytes int64
	MaxConnections  int
	Compression     bool
	CompressionAt   int
	CloseTimeout    time.Duration
	Authenticate    AuthenticateFunc
	AllowInsecure   bool
	AllowAnonymous  bool
	AllowAnyOrigin  bool
}

ServerConfig defines one explicit WebSocket HTTP upgrade boundary. TLS is required by default and is terminated by the caller-owned HTTP server.

type SessionHandler

type SessionHandler func(context.Context, *Connection) error

SessionHandler owns one accepted session until it returns.

type StatusCode

type StatusCode int

StatusCode identifies the limited close outcomes applications may emit.

const (
	// StatusNormalClosure indicates completed application work.
	StatusNormalClosure StatusCode = 1000
	// StatusGoingAway indicates lifecycle or caller cancellation.
	StatusGoingAway StatusCode = 1001
	// StatusPolicyViolation indicates rejected application input.
	StatusPolicyViolation StatusCode = 1008
	// StatusInternalError indicates an application handler failure.
	StatusInternalError StatusCode = 1011
)

Directories

Path Synopsis
cmd
starter-websocket-release command
Command starter-websocket-release builds deterministic signed library artifacts.
Command starter-websocket-release builds deterministic signed library artifacts.
internal
qualitygate command
Command qualitygate runs starter-websocket's repository-owned cross-platform checks.
Command qualitygate runs starter-websocket's repository-owned cross-platform checks.
release
Package release builds deterministic, signed source releases from exact Git commits.
Package release builds deterministic, signed source releases from exact Git commits.

Jump to

Keyboard shortcuts

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