Documentation
¶
Overview ¶
Package web provides the HTTP layer: a net/http server bound to a caller-supplied handler, RFC 9457 problem responses, a JSON writer, and the liveness and readiness endpoints an orchestrator probes.
Server ¶
NewServer wraps an http.Server built from a finalized Config and a handler the caller composes; an unfinalized Config panics with the fix named. Server.Start binds the listener on the calling goroutine — the context bounding the bind — and only then serves in the background, so a bind failure is returned to the caller instead of being lost in a goroutine. Server.Addr reports the bound address once started; a configured port 0 binds an ephemeral port, and Addr reads back the assignment.
Server.Err belongs to a successful serve session: after Start returns nil, a serve failure arrives on it, and the channel closes when serving stops. http.ErrServerClosed is the expected end of a shutdown and is not reported. Server.Shutdown before a successful Start is a no-op that leaves the server startable, so a lifecycle drain after a failed startup passes through cleanly; once it has served, a Server is single-use — construct a new one to serve again.
Lifecycle wiring ¶
The package registers no lifecycle hooks of its own and holds no shutdown timeout. Server.Start and Server.Shutdown carry the lifecycle package's hook signature, and Server.Err is a monitorable source, so a composition root wires the server as bare method values:
lc.OnStartup(srv.Start) lc.OnShutdown(srv.Shutdown) lc.Monitor(srv.Err())
A bind failure fails the coordinator's startup, a serve failure ends its run, and the shutdown hook receives the timeout-bounded drain context Server.Shutdown consumes directly.
Configuration ¶
Config holds the host, the port, and the server's four timeouts, and implements the config package's Merge and Finalize contract, so it loads as part of an application's configuration rather than on its own. The port and timeouts are pointers: nil is unset and takes the default, while an explicit zero survives the load and means what it says — a disabled timeout, or an ephemeral port. A file and the environment express both states identically. Finalize applies defaults, then the environment overrides named by the configuration's Env value, then validates. NewEnv composes the standard names from a prefix; an empty name disables that one override, and a zero Env means no environment overrides at all.
Health ¶
Liveness reports that the process is up and serving HTTP and checks nothing else; an unanswered probe is the liveness signal. Readiness aggregates the Check values the caller supplies — typically the lifecycle coordinator alongside each subsystem that reports readiness — and answers 503 unless every one of them is ready. A Check with a nil Checker reports not ready, so a subsystem that failed to construct fails the probe. RegisterHealth mounts both endpoints on a Mounter. The patterns use net/http.ServeMux's method-scoped syntax ("GET /healthz"), which http.ServeMux handles directly; any other Mounter translates them.
Middleware ¶
A Middleware wraps one http.Handler in another, and Chain composes a set of them around a handler in argument order: in Chain(h, a, b), a sees the request first. A nil entry is skipped, so a caller can build a chain with conditional entries without filtering it first.
RequestLogger emits one record per request through a *slog.Logger the caller supplies — method, path, status, duration, and remote address — at info level, or at error level with the panic value attached when the handler panics (the panic then continues to net/http's recovery). A successful request to HealthPath or ReadyPath logs at debug — orchestrator heartbeat, visible in development and quiet in production — while a failing probe stays at info. Beyond that the middleware does not judge status codes: whether a 5xx was the application's own failure belongs to the error mapping, not here.
The middleware wraps the ResponseWriter to capture the status. The wrapper records the first status written, implements Unwrap so flushing and hijacking work through http.ResponseController, and delegates io.ReaderFrom so a handler serving files keeps the zero-copy path. http.Pusher is not available through the wrapper.
Problem responses ¶
Error responses are RFC 9457 problem documents. The type member identifies the problem's semantics and is the member a client branches on, with title advisory and status an advisory copy of the status line. This package defines no type URIs of its own: every problem it emits is ProblemTypeBlank, and a consumer supplies its own URI through Problem.Write or the extras map of WriteProblemWith. Extras may add or override any member except status, which always matches the status line. A zero Status defaults to 500. An empty title defaults to the status phrase, and is omitted for a code outside the standard table.
Index ¶
- Constants
- func Chain(handler http.Handler, middleware ...Middleware) http.Handler
- func Liveness() http.Handler
- func Readiness(checks ...Check) http.Handler
- func RegisterHealth(m Mounter, checks ...Check)
- func WriteJSON(w http.ResponseWriter, status int, data any) error
- func WriteProblem(w http.ResponseWriter, r *http.Request, status int, title, detail string) error
- func WriteProblemWith(w http.ResponseWriter, r *http.Request, status int, title, detail string, ...) error
- type Check
- type Config
- type Env
- type Middleware
- type Mounter
- type Problem
- type Server
Constants ¶
const ( HealthPath = "/healthz" ReadyPath = "/readyz" )
The probe endpoints RegisterHealth mounts.
const ( // ProblemMediaType is the RFC 9457 problem document media type. ProblemMediaType = "application/problem+json" // ProblemTypeBlank is the type of a problem with no semantics beyond its // status code. ProblemTypeBlank = "about:blank" )
const JSONMediaType = "application/json"
JSONMediaType is the media type WriteJSON sets.
Variables ¶
This section is empty.
Functions ¶
func Chain ¶
func Chain(handler http.Handler, middleware ...Middleware) http.Handler
Chain composes middleware around handler in argument order: in Chain(h, a, b), a sees the request first. A nil entry is skipped, so a caller can build a chain with conditional entries without filtering it.
func Readiness ¶
Readiness aggregates the checks: 200 with each participant's state when all are ready, and otherwise a 503 problem document naming them. Zero checks report ready.
func RegisterHealth ¶
RegisterHealth mounts Liveness at GET /healthz and Readiness over checks at GET /readyz.
func WriteJSON ¶
func WriteJSON( w http.ResponseWriter, status int, data any, ) error
WriteJSON sends data as JSON with the given status.
func WriteProblem ¶
func WriteProblem( w http.ResponseWriter, r *http.Request, status int, title, detail string, ) error
WriteProblem sends a problem for the request, with Instance set to the request path and Problem.Write's defaults applied.
func WriteProblemWith ¶
func WriteProblemWith( w http.ResponseWriter, r *http.Request, status int, title, detail string, extras map[string]any, ) error
WriteProblemWith is WriteProblem with extra members copied over the document. Extras may add or override any member except status, which always matches the status line.
Types ¶
type Check ¶
type Check struct {
Name string
Checker lifecycle.ReadinessChecker
}
Check is one named participant in the readiness aggregate. A nil Checker reports not ready, so a subsystem that failed to construct fails the probe.
type Config ¶
type Config struct {
Host string `json:"host"`
Port *int `json:"port"`
ReadTimeout *config.Duration `json:"read_timeout"`
ReadHeaderTimeout *config.Duration `json:"read_header_timeout"`
WriteTimeout *config.Duration `json:"write_timeout"`
IdleTimeout *config.Duration `json:"idle_timeout"`
Env Env `json:"-"`
}
Config holds the server's address and timeouts. The port and timeouts are tri-state pointers: nil is unset and takes the default, while an explicit zero survives the load and means what it says — a disabled timeout, or an ephemeral port. Env names the environment variables Finalize reads; it is excluded from JSON, so only the composition root can set it.
type Env ¶
type Env struct {
Host string
Port string
ReadTimeout string
ReadHeaderTimeout string
WriteTimeout string
IdleTimeout string
}
Env names the environment variables Config.Finalize reads. An empty name disables that one override; the zero value disables all of them.
func NewEnv ¶
NewEnv composes the standard override names from a prefix: SERVER_HOST, SERVER_PORT, and the four timeout names under whatever prefix config.EnvName produces.
type Middleware ¶
Middleware wraps one http.Handler in another.
func RequestLogger ¶
func RequestLogger(logger *slog.Logger) Middleware
RequestLogger emits one record per request — method, path, status, duration, remote address — at info level. A successful request to HealthPath or ReadyPath logs at debug, keeping orchestrator heartbeat out of production logs while a failing probe stays visible; a panicking handler logs at error with the panic value attached before the panic continues to net/http's recovery. The wrapped ResponseWriter records the first status written, implements Unwrap so http.ResponseController reaches through it, and delegates io.ReaderFrom.
type Mounter ¶
Mounter mounts a handler on a method-scoped pattern ("GET /healthz"); *http.ServeMux satisfies it directly.
type Problem ¶
type Problem struct {
Type string `json:"type"`
Title string `json:"title,omitempty"`
Status int `json:"status"`
Detail string `json:"detail,omitempty"`
Instance string `json:"instance,omitempty"`
}
Problem is an RFC 9457 problem document. Type identifies the problem's semantics and is the member a client branches on; this package mints no type URIs of its own, so a consumer supplies one here or through the extras of WriteProblemWith.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server wraps an http.Server whose bind and serve phases are split, so a composition root registers Server.Start and Server.Shutdown as lifecycle hooks and monitors Server.Err.
func NewServer ¶
NewServer builds a Server from a finalized Config and the handler it serves. It panics if cfg has not been finalized.
func (*Server) Addr ¶
Addr reports the bound address once started — a configured port 0 reads back its assignment — and the configured address before.
func (*Server) Err ¶
Err delivers a serve failure after a successful Start; http.ErrServerClosed is the expected end of a shutdown and is not reported. The channel closes when serving stops.