web

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jul 28, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

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 Config and a handler the caller composes. Server.Start binds the listener on the calling goroutine 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, which lets a caller configure port 0 and read back the port that was assigned. A serve failure after startup arrives on Server.Err, a buffered channel that is closed when serving stops; http.ErrServerClosed is the expected end of a shutdown and is not reported.

Lifecycle wiring

The package registers no lifecycle hooks of its own and carries no shutdown timeout. A composition root wires Server.Start as a startup hook and Server.Shutdown as a shutdown hook, taking the coordinator's timeout-bounded drain context directly:

lc.OnStartup(func() {
	if err := srv.Start(); err != nil {
		log.Fatalf("http: %v", err)
	}
})
lc.OnShutdown(func(ctx context.Context) {
	if err := srv.Shutdown(ctx); err != nil {
		log.Printf("http: shutdown: %v", err)
	}
})

Configuration

Config holds the listen address 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. Finalize applies defaults, then the environment overrides named by the Env value the configuration carries, then validates. NewEnv composes the standard names from a prefix; an empty name disables that one override, so a zero Env means no environment overrides at all.

Health

Liveness reports that the process is up and serving HTTP and checks nothing else, which is what makes an unanswered probe the 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, which http.ServeMux satisfies incidentally.

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 info record per request — method, path, status, duration, and remote address — through a *slog.Logger the caller supplies. It takes the standard library's type rather than a constructed one, so the HTTP layer stays independent of how an application builds its logger. Every request logs at info: deciding that a 5xx deserves error level means knowing whether the status came from the application's own failure, which is a judgment the error-to-status mapping owns rather than the middleware.

The middleware wraps the ResponseWriter to capture the status. The wrapper records the first status written and implements Unwrap, so a handler reaches flushing and hijacking through http.ResponseController as if it were not wrapped.

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, because a type URI names an application's vocabulary rather than a library's: every problem it emits is ProblemTypeBlank, meaning no semantics beyond the HTTP status code, and a consumer supplies its own URI through Problem.Write or the extras map of WriteProblemWith. An empty title defaults to the status phrase, which is what RFC 9457 asks of an about:blank problem in any case.

Index

Constants

View Source
const (
	HealthPath = "/healthz"
	ReadyPath  = "/readyz"
)
View Source
const (
	ProblemMediaType = "application/problem+json"
	ProblemTypeBlank = "about:blank"
)
View Source
const JSONMediaType = "application/json"

Variables

This section is empty.

Functions

func Chain

func Chain(handler http.Handler, middleware ...Middleware) http.Handler

func Liveness

func Liveness() http.Handler

func Readiness

func Readiness(checks ...Check) http.Handler

func RegisterHealth

func RegisterHealth(m Mounter, checks ...Check)

func WriteJSON

func WriteJSON(
	w http.ResponseWriter,
	status int,
	data any,
) error

func WriteProblem

func WriteProblem(
	w http.ResponseWriter,
	r *http.Request,
	status int,
	title, detail string,
) error

func WriteProblemWith

func WriteProblemWith(
	w http.ResponseWriter,
	r *http.Request,
	status int,
	title, detail string,
	extras map[string]any,
) error

Types

type Check

type Check struct {
	Name    string
	Checker lifecycle.ReadinessChecker
}

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:"-"`
}

func (*Config) Addr

func (c *Config) Addr() string

func (*Config) Finalize

func (c *Config) Finalize() error

func (*Config) Merge

func (c *Config) Merge(src *Config)

type Env

type Env struct {
	Host              string
	Port              string
	ReadTimeout       string
	ReadHeaderTimeout string
	WriteTimeout      string
	IdleTimeout       string
}

func NewEnv

func NewEnv(prefix string) Env

type Middleware

type Middleware func(http.Handler) http.Handler

func RequestLogger

func RequestLogger(logger *slog.Logger) Middleware

type Mounter

type Mounter interface {
	Handle(pattern string, handler http.Handler)
}

type Problem

type Problem struct {
	Type     string `json:"type"`
	Title    string `json:"title"`
	Status   int    `json:"status"`
	Detail   string `json:"detail,omitempty"`
	Instance string `json:"instance,omitempty"`
}

func (Problem) Write

func (p Problem) Write(w http.ResponseWriter) error

type Server

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

func NewServer

func NewServer(cfg Config, handler http.Handler) *Server

func (*Server) Addr

func (s *Server) Addr() string

func (*Server) Err

func (s *Server) Err() <-chan error

func (*Server) Shutdown

func (s *Server) Shutdown(ctx context.Context) error

func (*Server) Start

func (s *Server) Start() error

Jump to

Keyboard shortcuts

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