httpserver

httpserver assembles an HTTP server from options and a route binder, validating configuration at startup and shutting down under a bounded budget.

Part of nurago, a collection of independent Go packages for backend services.

import "github.com/tecnickcom/nurago/pkg/httpserver"

Builds and runs the HTTP server: your routes from a Binder, the operational routes from a built-in set, middleware, and a shutdown path that waits for in-flight work.

srv, err := httpserver.New(ctx, &binder{},
    httpserver.WithServerAddr(":8080"),
    httpserver.WithEnableDefaultRoutes(httpserver.PingRoute, httpserver.StatusRoute),
    httpserver.WithRequestTimeout(30*time.Second),
    httpserver.WithShutdownTimeout(10*time.Second),
)

srv.StartServer()

Misconfiguration is an error from New, not a panic

Nil handlers, nil middleware, duplicate routes, malformed routes and unknown default-route identifiers are all reported by New as wrapped sentinel errors.

This is the difference between a bad deployment failing at rollout and failing on the first request that happens to hit the broken route. A typo in a route pattern or a nil handler behind a conditional is caught before the listener is bound, so the process exits and the orchestrator does not route traffic to it.

Wrapped sentinels mean a caller can distinguish the cases with errors.Is rather than matching on message text.

The operational routes

Enabled selectively or all at once:

RoutePurpose
/pingliveness, no dependency checks
/statusreadiness, runs the health checks
/metricsmetrics payload, 501 until a backend replaces it
/pprof/*optionprofiling
/ipthe service’s public address
/generated route index

Two of them behave differently from the rest.

The pprof route has its request timeout disabled, because /pprof/profile and /pprof/trace block for their requested duration by design. A 30-second CPU profile under a 30-second request timeout would be truncated. See profiling.

/metrics answers 501 until a metrics backend is wired, so the route exists from the start and the scrape configuration does not need to change when instrumentation is added.

/pprof, /metrics and /status disclose internals and should not be publicly reachable.

Lifecycle

StartServer does not block: the listener is bound before it returns, so a subsequent readiness check cannot race the socket. HTTPServer.Addr reports the bound address, which is how a test using port 0 discovers the ephemeral port.

Shutdown is driven by context cancellation or a signal channel, bounded by WithShutdownTimeout, and integrates with an external sync.WaitGroup so background workers registered elsewhere are waited for too. HTTPServer.ServeError reports abnormal termination.

The waitgroup integration is what makes shutdown correct rather than merely graceful. Draining in-flight HTTP requests while a periodic task is midway through writing is only half the job.

The timeout lattice

Several timeouts apply at once: read, read-header, write, idle, the per-request timeout, and the shutdown budget. They interact, and two relationships matter most.

The request timeout must exceed anything a handler legitimately does, including the retries a handler performs through httpretrier, or the handler is cut off mid-retry.

The shutdown timeout must exceed the request timeout, or shutdown kills requests that were still inside their own budget.

Routes carry a description

httpserver.Route includes a Description, which feeds the generated index at /. It costs a line per route and produces a self-describing service, which is the documentation most likely to stay current because it lives next to the handler.

Dependencies

Reaches github.com/julienschmidt/httprouter, plus httputil, healthcheck, profiling and ipify.