Name
go-httpserver — Public documentation for gomatic/go-httpserver — a stdlib-only HTTP server lifecycle wrapper with context-driven graceful shutdown.
go-httpserver is the gomatic ecosystem’s HTTP server lifecycle wrapper for Go. It wraps an *http.Server and owns the start/stop machinery — listen, context-cancellation shutdown, and the start/shutdown error contract — and nothing else. The caller supplies the http.Handler and wires the cancellation (typically via signal.NotifyContext); a single Serve call then blocks until the context is cancelled or startup fails, shutting the server down gracefully within a supplied timeout. It is stdlib-only and reusable by any service that needs to serve HTTP and stop cleanly.
- Source:
gomatic/go-httpserver - API reference: pkg.go.dev/github.com/gomatic/go-httpserver
Install
go get github.com/gomatic/go-httpserverWhy a lifecycle wrapper
Serving HTTP cleanly is more than ListenAndServe: a real service must shut down gracefully on a signal, bound that shutdown by a deadline, and report a startup failure without letting a clean shutdown mask it. Hand-rolling that dance — a goroutine, an error channel, a select on context cancellation, and the right error precedence — is repetitive and easy to get subtly wrong. go-httpserver owns that machinery once, so every gomatic service starts and stops identically.
The library owns the lifecycle only — it ships no routing, middleware, or handler logic. The caller supplies the handler and the cancellation context:
import "github.com/gomatic/go-httpserver"
// Bind host:port, serve a handler, and stop when ctx is cancelled.
srv := httpserver.New(slog.Default(), "127.0.0.1", 8080, handler)
err := srv.Serve(ctx, 5*time.Second)Usage
Serve until a signal
The caller wires cancellation — typically with signal.NotifyContext — and passes the resulting context to Serve. When the context is cancelled (e.g. on SIGINT/SIGTERM), the server shuts down gracefully within the supplied timeout.
package main
import (
"context"
"log/slog"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/gomatic/go-httpserver"
)
func main() {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
handler := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
})
srv := httpserver.New(slog.Default(), "127.0.0.1", 8080, handler)
if err := srv.Serve(ctx, 5*time.Second); err != nil {
slog.Error("server failed", "error", err)
os.Exit(1)
}
}Serve blocks until the context is cancelled or startup fails. Request contexts derive from the lifecycle context (via http.Server.BaseContext), so in-flight handlers observe the same cancellation.
Matching the lifecycle errors
The package declares two sentinels and wraps the underlying cause with go-error, so callers match the identity of the failure with errors.Is — never by string:
import (
"errors"
"github.com/gomatic/go-httpserver"
)
err := srv.Serve(ctx, 5*time.Second)
switch {
case errors.Is(err, httpserver.ErrServerStart):
// the server could not start listening (e.g. port in use)
case errors.Is(err, httpserver.ErrServerShutdown):
// the server did not shut down within the deadline
}Because the cause is joined with %w, the wrapped error (e.g. the original net bind error) also remains recoverable with errors.Is.
Inspecting the configured server
New returns a *Server exposing two read accessors:
srv := httpserver.New(slog.Default(), "127.0.0.1", 8080, handler)
srv.Addr() // "127.0.0.1:8080" — the configured listen address
srv.Handler() // the http.Handler the server servesDesign
- Lifecycle only.
Serverwraps an*http.Serverand owns start, context-cancellation shutdown, and the error contract — nothing else. Routing and middleware belong to the caller’s handler. - Startup failure is never masked. When the context is cancelled, a pending startup error is preferred over a clean shutdown result, so a real failure (e.g. a port already in use) always surfaces.
- Shared error mechanism.
ErrServerStartandErrServerShutdownare declared on thego-errorconstant/sentinel mechanism, matchable witherrors.Isand wrapping their cause with%w. - Named domain types.
HostandPortname the bind address parameters instead of barestring/int. - Slowloris-resistant. A
ReadHeaderTimeoutbounds how long the server waits for request headers, preventing connections that hold open by trickling headers (gosec G112). - Dependency-light. The package depends only on the standard library plus
gomatic/go-errorfor its sentinels.
Who uses it
Every gomatic Go service that serves HTTP embeds this wrapper for its start/stop lifecycle, alongside the other gomatic/go-* libraries.