Documentation
¶
Overview ¶
Package http provides an HTTP server and client toolkit for GTB services.
The server side bootstraps an HTTP server that integrates with the pkg/controls lifecycle and exposes health, liveness, and readiness endpoints, plus composable server middleware: authentication (AuthMiddleware), rate limiting, security headers, OpenTelemetry, and logging. Servers are constructed from package-owned ServerSettings. GTB config integration lives in adapter helpers such as ServerSettingsFromConfig, ObserveServerSettingsFromConfig, NewServerFromContainable, and RegisterFromContainable, so the core constructors remain independent of the framework config container.
The client side (NewClient) provides a configurable HTTP client with retry, rate-limiting, redirect control, and circuit-breaker middleware.
Index ¶
- Constants
- Variables
- func ClientIPKey(r *http.Request) string
- func HealthHandler(controller controls.HealthReporter) http.HandlerFunc
- func IdentityFromContext(ctx context.Context) (*authn.Identity, bool)
- func LivenessHandler(controller controls.HealthReporter) http.HandlerFunc
- func MaxBytesMiddleware(maxBytes int64) func(http.Handler) http.Handler
- func NewClient(opts ...ClientOption) *http.Client
- func NewServer(ctx context.Context, settings ServerSettings, handler http.Handler, ...) (*http.Server, error)
- func NewServerFromContainable(ctx context.Context, cfg config.Containable, handler http.Handler, ...) (*http.Server, error)
- func NewTransport(tlsCfg *tls.Config) *http.Transport
- func ObserveServerSettingsFromConfig(cfg config.Containable, prefix string, ...) (*config.ObservedSection[ServerSettings], error)
- func ReadinessHandler(controller controls.HealthReporter) http.HandlerFunc
- func Register(ctx context.Context, id string, controller controls.Controllable, ...) (*http.Server, error)
- func RegisterFromContainable(ctx context.Context, id string, controller controls.Controllable, ...) (*http.Server, error)
- func StartFromContainable(cfg config.Containable, log logger.Logger, srv *http.Server, ...) controls.StartFunc
- func StartWithTLSPair(logger *slog.Logger, srv *http.Server, tlsPair gtbtls.Pair) controls.StartFunc
- func Status(srv *http.Server) controls.StatusFunc
- func Stop(logger *slog.Logger, srv *http.Server) controls.StopFunc
- type AuthOption
- func WithAPIKeyHeader(header string, v authn.Verifier) AuthOption
- func WithAuthLogger(l *slog.Logger) AuthOption
- func WithAuthSkipper(pred func(*http.Request) bool) AuthOption
- func WithAuthorize(fn authn.AuthorizeFunc) AuthOption
- func WithBearerVerifier(v authn.Verifier) AuthOption
- func WithCookieVerifier(cookieName string, v authn.Verifier) AuthOption
- func WithMTLSVerifier(v authn.CertVerifier) AuthOption
- type Chain
- type CircuitBreakerConfig
- type CircuitBreakerConfigOverrides
- type CircuitState
- type ClientChain
- type ClientMiddleware
- func WithBasicAuth(username, password string) ClientMiddleware
- func WithBearerToken(token string) ClientMiddleware
- func WithCircuitBreaker(log *slog.Logger, cfg CircuitBreakerConfig) ClientMiddleware
- func WithRateLimit(requestsPerSecond float64) ClientMiddleware
- func WithRequestLogging(log *slog.Logger) ClientMiddleware
- type ClientOption
- func WithCertPool(pool *x509.CertPool) ClientOption
- func WithClientMiddleware(chain ClientChain) ClientOption
- func WithMaxRedirects(n int) ClientOption
- func WithRetry(cfg RetryConfig) ClientOption
- func WithTLSConfig(cfg *tls.Config) ClientOption
- func WithTimeout(d time.Duration) ClientOption
- func WithTransport(rt http.RoundTripper) ClientOption
- type LogFormat
- type LoggingOption
- func WithFormat(format LogFormat) LoggingOption
- func WithHeaderFields(headers ...string) LoggingOption
- func WithLogLevel(level slog.Level) LoggingOption
- func WithPathFilter(paths ...string) LoggingOption
- func WithTrustedProxy() LoggingOption
- func WithoutLatency() LoggingOption
- func WithoutUserAgent() LoggingOption
- type Middleware
- func AuthMiddleware(opts ...AuthOption) (Middleware, error)
- func LoggingMiddleware(l *slog.Logger, opts ...LoggingOption) Middleware
- func OTelMiddleware(server string, opts ...otelhttp.Option) Middleware
- func RateLimitMiddleware(log *slog.Logger, cfg RateLimitConfig) Middleware
- func SecurityHeadersMiddleware(opts ...SecurityHeadersOption) Middleware
- type RateLimitConfig
- type RateLimitConfigOverrides
- type RegisterOption
- type RetryConfig
- type SecurityHeadersOption
- func WithContentSecurityPolicy(policy string) SecurityHeadersOption
- func WithContentTypeOptions(value string) SecurityHeadersOption
- func WithFrameOptions(value string) SecurityHeadersOption
- func WithHSTS(maxAge time.Duration, includeSubdomains, preload bool) SecurityHeadersOption
- func WithReferrerPolicy(policy string) SecurityHeadersOption
- type ServerOption
- func WithConfigPrefix(prefix string) ServerOption
- func WithIdleTimeout(d time.Duration) ServerOption
- func WithMaxHeaderBytes(n int) ServerOption
- func WithPort(port int) ServerOption
- func WithReadTimeout(d time.Duration) ServerOption
- func WithServerTLSConfig(c *tls.Config) ServerOption
- func WithWriteTimeout(d time.Duration) ServerOption
- type ServerSettings
- type ServerSettingsSource
Examples ¶
Constants ¶
const ( // StateClosed admits all requests; failures are counted. StateClosed = CircuitState(circuitbreaker.StateClosed) // StateOpen rejects all requests immediately with ErrCircuitOpen until the // cooldown elapses, then transitions to StateHalfOpen. StateOpen = CircuitState(circuitbreaker.StateOpen) // StateHalfOpen admits a limited number of trial requests; success closes // the breaker, failure re-opens it. StateHalfOpen = CircuitState(circuitbreaker.StateHalfOpen) )
The public states are derived from the shared core so the two enumerations can never silently drift out of order.
const DefaultConfigPrefix = "server.http"
DefaultConfigPrefix is the config prefix an HTTP server reads (port, TLS, max_header_bytes) unless overridden with WithConfigPrefix.
const ( // DefaultMaxRequestBodyBytes caps the size of each request body // accepted by the management HTTP server. Closes M-1 from // docs/development/reports/security-audit-2026-04-17.md. DefaultMaxRequestBodyBytes int64 = 1 << 20 // 1 MiB )
Variables ¶
var ErrCircuitOpen = errors.New("http: circuit breaker is open")
ErrCircuitOpen is returned by the breaker when it is open. Callers may test for it with errors.Is. It is returned directly (not stack-wrapped per call): an open breaker is an expected control-flow signal on a high-volume reject path, not an exceptional error needing a fresh stack each time.
Functions ¶
func ClientIPKey ¶ added in v0.23.0
ClientIPKey is a ready-made RateLimitConfig.KeyFunc that keys on the client IP using the connection's RemoteAddr.
It deliberately does NOT trust X-Forwarded-For / X-Real-IP: those headers are spoofable by any direct client, so keying a limiter on them would let an attacker both evade their own bucket and churn the bounded key store by rotating fake IPs. A server behind a trusted reverse proxy that terminates XFF should supply its own KeyFunc that reads the proxy-set header. This reuses the logging middleware's client-IP derivation with trustedProxy=false, the same safe default.
func HealthHandler ¶
func HealthHandler(controller controls.HealthReporter) http.HandlerFunc
HealthHandler returns an http.HandlerFunc that responds with the controller's health report.
func IdentityFromContext ¶ added in v0.23.0
IdentityFromContext returns the verified Identity set by AuthMiddleware. The same key is shared with the gRPC interceptor, so a handler reads identity the same way regardless of transport.
func LivenessHandler ¶
func LivenessHandler(controller controls.HealthReporter) http.HandlerFunc
LivenessHandler returns an http.HandlerFunc that responds with the controller's liveness report.
func MaxBytesMiddleware ¶
MaxBytesMiddleware wraps a handler so every request body is bounded by http.MaxBytesReader. A request that exceeds the limit is terminated with HTTP 413 (via the default ResponseWriter behaviour) when the handler attempts to read past the boundary.
Callers that need per-route limits should wrap the handler directly rather than registering at server level.
func NewClient ¶
func NewClient(opts ...ClientOption) *http.Client
NewClient returns an *http.Client with security-focused defaults: TLS 1.2 minimum, curated cipher suites, timeouts, connection limits, and redirect policy that rejects HTTPS-to-HTTP downgrades.
Example ¶
package main
import (
"time"
gtbhttp "gitlab.com/phpboyscout/go-tool-base/pkg/http"
)
func main() {
// Create a hardened HTTP client with security defaults.
client := gtbhttp.NewClient(
gtbhttp.WithTimeout(10*time.Second),
gtbhttp.WithMaxRedirects(5),
)
_ = client // Use like a standard *http.Client
}
Output:
Example (WithRetry) ¶
package main
import (
"time"
gtbhttp "gitlab.com/phpboyscout/go-tool-base/pkg/http"
)
func main() {
// Create a client with automatic retry for transient failures.
client := gtbhttp.NewClient(
gtbhttp.WithTimeout(30*time.Second),
gtbhttp.WithRetry(gtbhttp.RetryConfig{
MaxRetries: 3,
InitialBackoff: 500 * time.Millisecond,
MaxBackoff: 30 * time.Second,
}),
)
_ = client
}
Output:
func NewServer ¶
func NewServer(ctx context.Context, settings ServerSettings, handler http.Handler, opts ...ServerOption) (*http.Server, error)
NewServer returns a new preconfigured http.Server from explicit typed settings.
func NewServerFromContainable ¶ added in v0.30.0
func NewServerFromContainable(ctx context.Context, cfg config.Containable, handler http.Handler, opts ...ServerOption) (*http.Server, error)
NewServer returns a new preconfigured http.Server. With no options it reads from the default "server.http" config prefix; pass ServerOption values such as WithConfigPrefix or WithPort to run multiple independent servers.
func NewTransport ¶
NewTransport returns a preconfigured *http.Transport with security-focused defaults: curated TLS configuration, connection limits, and timeouts. If tlsCfg is nil, DefaultTLSConfig() is used.
func ObserveServerSettingsFromConfig ¶ added in v0.30.0
func ObserveServerSettingsFromConfig( cfg config.Containable, prefix string, opts ...config.SectionBindingOption[ServerSettings], ) (*config.ObservedSection[ServerSettings], error)
ObserveServerSettingsFromConfig binds HTTP server settings to cfg and keeps a typed snapshot rehydrated after successful config reloads.
func ReadinessHandler ¶
func ReadinessHandler(controller controls.HealthReporter) http.HandlerFunc
ReadinessHandler returns an http.HandlerFunc that responds with the controller's readiness report.
func Register ¶
func Register( ctx context.Context, id string, controller controls.Controllable, logger *slog.Logger, handler http.Handler, settings ServerSettings, tlsPair gtbtls.Pair, opts ...any, ) (*http.Server, error)
Register creates a new HTTP server from explicit typed settings and registers it with the controller under the given id.
func RegisterFromContainable ¶ added in v0.30.0
func RegisterFromContainable(ctx context.Context, id string, controller controls.Controllable, cfg config.Containable, log logger.Logger, handler http.Handler, opts ...any) (*http.Server, error)
Register creates a new HTTP server and registers it with the controller under the given id. The opts variadic accepts both ServerOption values (port, prefix, timeouts) and RegisterOption values (middleware, body limit) — other types are ignored. This mirrors the pkg/grpc Register signature.
func StartFromContainable ¶ added in v0.30.0
func StartFromContainable(cfg config.Containable, log logger.Logger, srv *http.Server, opts ...ServerOption) controls.StartFunc
Start returns a curried function suitable for use with the controls package. With no options it reads TLS from the default "server.http" config prefix; pass WithConfigPrefix to match a server constructed on a custom prefix.
func StartWithTLSPair ¶ added in v0.30.0
func StartWithTLSPair(logger *slog.Logger, srv *http.Server, tlsPair gtbtls.Pair) controls.StartFunc
StartWithTLSPair returns a curried function suitable for use with the controls package from explicit TLS settings.
func Status ¶
func Status(srv *http.Server) controls.StatusFunc
Status returns a curried health function for a manually-wired server. It reports an error only when srv is nil; use the controller wiring (Serve) for serve-goroutine death detection.
func Stop ¶
Stop returns a curried function suitable for use with the controls package. Shutdown is attempted first to drain in-flight requests. If the shutdown context expires (or Shutdown otherwise errors) the server is force-closed via Close so a hung handler cannot leave the listener and connections open, mirroring the gRPC transport's graceful-then-force-stop behaviour.
Types ¶
type AuthOption ¶ added in v0.23.0
type AuthOption func(*authConfig)
AuthOption configures AuthMiddleware.
func WithAPIKeyHeader ¶ added in v0.23.0
func WithAPIKeyHeader(header string, v authn.Verifier) AuthOption
WithAPIKeyHeader extracts the credential from the named header (e.g. "X-API-Key") and verifies it with v.
func WithAuthLogger ¶ added in v0.23.0
func WithAuthLogger(l *slog.Logger) AuthOption
WithAuthLogger sets the logger for redacted server-side auth failure logging.
func WithAuthSkipper ¶ added in v0.23.0
func WithAuthSkipper(pred func(*http.Request) bool) AuthOption
WithAuthSkipper skips auth for requests matching pred (e.g. an OPTIONS preflight or a public sub-path). Health endpoints are already outside the chain and need no skipper.
func WithAuthorize ¶ added in v0.23.0
func WithAuthorize(fn authn.AuthorizeFunc) AuthOption
WithAuthorize installs an authorization predicate run after verification.
func WithBearerVerifier ¶ added in v0.23.0
func WithBearerVerifier(v authn.Verifier) AuthOption
WithBearerVerifier extracts a token from "Authorization: Bearer <token>" and verifies it with v.
func WithCookieVerifier ¶ added in v0.24.0
func WithCookieVerifier(cookieName string, v authn.Verifier) AuthOption
WithCookieVerifier extracts the credential from the named cookie and verifies it with v. The cookie is an AMBIENT credential — the browser sends it on every request, including <img>/<audio>/<video> sub-resource loads that cannot set an Authorization header — so it sits BELOW the explicit header schemes in precedence: an explicit bearer or API-key header always wins, and the cookie is consulted only when no header credential is presented. This lets a browser session authenticate sub-resources while leaving explicit API clients unaffected. Typically paired with a token-in-URL bootstrap that sets the cookie on first load (Jupyter-style).
func WithMTLSVerifier ¶ added in v0.23.0
func WithMTLSVerifier(v authn.CertVerifier) AuthOption
WithMTLSVerifier authenticates the request from its verified client certificate when no header credential is presented. The server must be configured for client-cert verification (RequireAndVerifyClientCert).
type Chain ¶
type Chain struct {
// contains filtered or unexported fields
}
Chain composes zero or more Middleware into a single Middleware. Middleware is applied left-to-right: the first middleware in the list is the outermost wrapper (first to see the request, last to see the response).
chain := NewChain(recovery, logging, auth) handler := chain.Then(mux)
func NewChain ¶
func NewChain(middlewares ...Middleware) Chain
NewChain creates a new middleware chain from the given middleware functions. Nil entries are silently skipped.
func (Chain) Append ¶
func (c Chain) Append(middlewares ...Middleware) Chain
Append returns a new Chain with additional middleware appended. The original chain is not modified. Nil entries are silently skipped.
type CircuitBreakerConfig ¶ added in v0.23.0
type CircuitBreakerConfig struct {
// FailureThreshold is the number of consecutive failures (within Closed)
// that trips the breaker open. Must be >= 1. Default: 5.
FailureThreshold int `mapstructure:"failure_threshold" yaml:"failure_threshold" json:"failure_threshold"`
// Cooldown is how long the breaker stays Open before allowing a trial.
// Default: 30s.
Cooldown time.Duration `mapstructure:"cooldown" yaml:"cooldown" json:"cooldown"`
// HalfOpenMaxRequests is the number of trial requests allowed in HalfOpen.
// The first success closes the breaker; any failure re-opens it.
// Must be >= 1. Default: 1.
HalfOpenMaxRequests int `mapstructure:"half_open_max_requests" yaml:"half_open_max_requests" json:"half_open_max_requests"`
// IsFailure classifies a round-trip outcome as a failure for breaker
// accounting. When nil, the default treats transport errors and 5xx
// responses (>=500) as failures; 4xx and 2xx/3xx are successes. A 429
// (client rate-limited) therefore does NOT trip the breaker — that is
// retry's job, not the breaker's.
IsFailure func(resp *http.Response, err error) bool `mapstructure:"-" yaml:"-" json:"-"`
// OnStateChange is invoked on every state transition. Optional; transitions
// are also logged via the constructor's logger.
OnStateChange func(from, to CircuitState) `mapstructure:"-" yaml:"-" json:"-"`
}
CircuitBreakerConfig configures the client-side circuit breaker.
func CircuitBreakerConfigFromConfig ¶ added in v0.23.0
func CircuitBreakerConfigFromConfig(cfg config.Containable, prefix string) CircuitBreakerConfig
CircuitBreakerConfigFromConfig builds a CircuitBreakerConfig from the config layer under "<prefix>.circuitbreaker.*" (prefix defaults to "server.http").
Unset keys keep their DefaultCircuitBreakerConfig values. The code-only fields (IsFailure, OnStateChange) are never read from config.
func DefaultCircuitBreakerConfig ¶ added in v0.23.0
func DefaultCircuitBreakerConfig() CircuitBreakerConfig
DefaultCircuitBreakerConfig returns: threshold 5, cooldown 30s, half-open trial 1, default 5xx/transport-error failure classification.
func MergeCircuitBreakerConfig ¶ added in v0.30.0
func MergeCircuitBreakerConfig(base, override CircuitBreakerConfig, fields CircuitBreakerConfigOverrides) CircuitBreakerConfig
MergeCircuitBreakerConfig applies explicitly supplied typed override values to base while leaving code-only function fields under caller control.
type CircuitBreakerConfigOverrides ¶ added in v0.30.0
type CircuitBreakerConfigOverrides struct {
FailureThreshold bool
Cooldown bool
HalfOpenMaxRequests bool
}
CircuitBreakerConfigOverrides records which typed circuit breaker config fields were explicitly supplied by an adapter.
type CircuitState ¶ added in v0.23.0
type CircuitState int
CircuitState is the client circuit breaker's state.
func (CircuitState) String ¶ added in v0.23.0
func (s CircuitState) String() string
String renders the state for logging.
type ClientChain ¶
type ClientChain struct {
// contains filtered or unexported fields
}
ClientChain composes ClientMiddleware in order. Immutable — Append returns a new chain.
func NewClientChain ¶
func NewClientChain(middlewares ...ClientMiddleware) ClientChain
NewClientChain creates a ClientChain from the given middleware.
Example ¶
package main
import (
"os"
"time"
gtbhttp "gitlab.com/phpboyscout/go-tool-base/pkg/http"
"gitlab.com/phpboyscout/go-tool-base/pkg/logger"
)
func main() {
// Compose client middleware for auth, logging, and rate limiting.
chain := gtbhttp.NewClientChain(
gtbhttp.WithRequestLogging(logger.ToSlog(logger.NewNoop())),
gtbhttp.WithBearerToken(os.Getenv("API_TOKEN")),
gtbhttp.WithRateLimit(10), // 10 requests per second
)
client := gtbhttp.NewClient(
gtbhttp.WithTimeout(30*time.Second),
gtbhttp.WithClientMiddleware(chain),
)
_ = client // Use like a standard *http.Client
}
Output:
func (ClientChain) Append ¶
func (c ClientChain) Append(middlewares ...ClientMiddleware) ClientChain
Append returns a new chain with additional middleware appended.
func (ClientChain) Then ¶
func (c ClientChain) Then(rt http.RoundTripper) http.RoundTripper
Then applies the middleware chain to the given RoundTripper and returns the wrapped result.
type ClientMiddleware ¶
type ClientMiddleware func(next http.RoundTripper) http.RoundTripper
ClientMiddleware wraps an http.RoundTripper with additional behaviour. The first middleware in a chain is the outermost wrapper — it executes first on the request and last on the response.
func WithBasicAuth ¶
func WithBasicAuth(username, password string) ClientMiddleware
WithBasicAuth returns middleware that injects an Authorization: Basic header. The header is only sent to the first host the client addresses, so a cross-host redirect cannot capture the credential.
func WithBearerToken ¶
func WithBearerToken(token string) ClientMiddleware
WithBearerToken returns middleware that injects an Authorization: Bearer header. The header is only sent to the first host the client addresses, so a cross-host redirect cannot capture the token.
func WithCircuitBreaker ¶ added in v0.23.0
func WithCircuitBreaker(log *slog.Logger, cfg CircuitBreakerConfig) ClientMiddleware
WithCircuitBreaker returns a ClientMiddleware that fails fast while a downstream is consistently failing, avoiding wasted retry/backoff cycles.
Place it OUTSIDE the retry transport — i.e. in the ClientChain via WithClientMiddleware, which wraps the transport after retry — so the breaker sees the final post-retry verdict: one retry-exhausted logical call counts as a single breaker failure, not one per attempt. Once Open, calls are rejected before entering the retry layer, so no backoff sleeps are spent on a service known to be down.
func WithRateLimit ¶
func WithRateLimit(requestsPerSecond float64) ClientMiddleware
WithRateLimit returns middleware that limits outbound requests to the specified rate using a token-bucket limiter (burst 1). Blocks until a token is available or the request context is cancelled. The limiter is shared across all requests through the transport, so it holds under concurrency — the previous hand-rolled version let concurrent goroutines sleep in parallel and then proceed together, admitting a burst per interval.
func WithRequestLogging ¶
func WithRequestLogging(log *slog.Logger) ClientMiddleware
WithRequestLogging returns middleware that logs each outbound request and response at debug level. Logs method, URL, status code, and duration. Headers and body are NOT logged for security.
type ClientOption ¶
type ClientOption func(*clientConfig)
ClientOption configures the secure HTTP client.
func WithCertPool ¶ added in v0.8.0
func WithCertPool(pool *x509.CertPool) ClientOption
WithCertPool sets the root CA pool used to verify server certificates, preserving the hardened default TLS configuration (cipher suites, minimum version, curve preferences). Use this to trust certificates that are not in the system roots, such as a private CA or self-signed cert. Build the pool with tls.CertPool. Applying WithTLSConfig after this option replaces the pool along with the rest of the TLS configuration.
func WithClientMiddleware ¶
func WithClientMiddleware(chain ClientChain) ClientOption
WithClientMiddleware applies a middleware chain to the client's transport. The chain wraps the transport after retry (if configured) so that retry operates on the raw transport, not on logged/authed requests.
func WithMaxRedirects ¶
func WithMaxRedirects(n int) ClientOption
WithMaxRedirects sets the maximum number of redirects to follow. Default: 10. Set to 0 to disable redirect following entirely.
func WithRetry ¶
func WithRetry(cfg RetryConfig) ClientOption
WithRetry enables automatic retry with exponential backoff for transient failures.
func WithTLSConfig ¶
func WithTLSConfig(cfg *tls.Config) ClientOption
WithTLSConfig overrides the default TLS configuration. The caller is responsible for ensuring the provided config meets security requirements.
func WithTimeout ¶
func WithTimeout(d time.Duration) ClientOption
WithTimeout sets the overall request timeout. Default: 30s.
func WithTransport ¶
func WithTransport(rt http.RoundTripper) ClientOption
WithTransport overrides the entire HTTP transport. When set, transport-level options (TLS config, connection limits) are ignored.
type LogFormat ¶
type LogFormat int
LogFormat controls the output format of the logging middleware.
const ( // FormatStructured emits structured key-value fields via *slog.Logger. FormatStructured LogFormat = iota // FormatCommon emits NCSA Common Log Format (CLF). FormatCommon // FormatCombined emits NCSA Combined Log Format (CLF + Referer + User-Agent). FormatCombined // FormatJSON emits a single JSON object per request. FormatJSON )
type LoggingOption ¶
type LoggingOption func(*loggingConfig)
LoggingOption configures transport logging behaviour.
func WithFormat ¶
func WithFormat(format LogFormat) LoggingOption
WithFormat sets the log output format. Defaults to FormatStructured.
func WithHeaderFields ¶
func WithHeaderFields(headers ...string) LoggingOption
WithHeaderFields logs the specified request header values as fields. Header names are normalised to lowercase. Values are truncated to 256 bytes.
Known-sensitive headers (Authorization, Cookie, Set-Cookie, X-Api-Key, X-Auth-Token, X-Csrf-Token, X-Session-Token, Proxy-Authorization) are always redacted regardless of whether they appear in the fields list. This is defence-in-depth against accidental credential leakage.
func WithLogLevel ¶
func WithLogLevel(level slog.Level) LoggingOption
WithLogLevel sets the log level for successful requests. Defaults to slog.LevelInfo. Errors always log at slog.LevelError.
func WithPathFilter ¶
func WithPathFilter(paths ...string) LoggingOption
WithPathFilter excludes requests matching the given paths from logging.
func WithTrustedProxy ¶ added in v0.17.0
func WithTrustedProxy() LoggingOption
WithTrustedProxy makes the logging middleware trust the client-supplied X-Forwarded-For and X-Real-IP headers when deriving the logged client IP.
These headers are trivially spoofable by any direct client, so they are IGNORED by default and the connection's RemoteAddr is logged instead. Enable this option only when the server sits behind a trusted reverse proxy or load balancer that overwrites (rather than appends to) these headers. Enabling it on a directly-exposed server lets clients forge the recorded client IP.
func WithoutLatency ¶
func WithoutLatency() LoggingOption
WithoutLatency disables the "latency" field.
func WithoutUserAgent ¶
func WithoutUserAgent() LoggingOption
WithoutUserAgent disables the "user_agent" field.
type Middleware ¶
Middleware is the standard Go HTTP middleware signature.
func AuthMiddleware ¶ added in v0.23.0
func AuthMiddleware(opts ...AuthOption) (Middleware, error)
AuthMiddleware returns a Middleware that authenticates (and optionally authorizes) each request, storing the verified Identity in the request context on success. With no verifier configured it is a construction error (fail-closed; never a silent pass-through).
Credential precedence: a bearer token and an API-key header presented together are rejected as ambiguous (fail-closed). Otherwise the presented header scheme is used; mTLS authenticates only when no header credential is presented.
On failure the middleware writes a generic 401 (with WWW-Authenticate) or 403 and never discloses why — the specific cause is logged once at WARN with the credential redacted. The handler is not invoked on failure.
func LoggingMiddleware ¶
func LoggingMiddleware(l *slog.Logger, opts ...LoggingOption) Middleware
LoggingMiddleware returns an HTTP Middleware that logs each completed request.
func OTelMiddleware ¶ added in v0.7.0
func OTelMiddleware(server string, opts ...otelhttp.Option) Middleware
OTelMiddleware returns a Middleware that records an OpenTelemetry server span and the standard server metrics (http.server.*) for each request, reading whichever TracerProvider and MeterProvider are installed as the OTel globals (see telemetry.Setup). server names the span operation, identifying this service in the trace.
It composes in a Chain like any other middleware. Put it ahead of the logging middleware so the request log can pick up the active span:
chain := http.NewChain(
http.OTelMiddleware("macguffin"),
http.LoggingMiddleware(log),
)
func RateLimitMiddleware ¶ added in v0.23.0
func RateLimitMiddleware(log *slog.Logger, cfg RateLimitConfig) Middleware
RateLimitMiddleware returns a Middleware that admits requests under a token-bucket limiter and rejects excess traffic with 429 Too Many Requests plus a Retry-After header (which a GTB client's retry layer honours). An invalid config is clamped to defaults rather than rejected.
Because it is an ordinary Middleware it composes into any Chain and can be scoped globally (one entry in the server chain) or per-route (wrap a single handler). Per-client limiting is enabled by setting RateLimitConfig.KeyFunc.
Health endpoints (/healthz, /livez, /readyz) are mounted outside the WithMiddleware chain by Register, so a global limiter never throttles probes.
func SecurityHeadersMiddleware ¶ added in v0.17.0
func SecurityHeadersMiddleware(opts ...SecurityHeadersOption) Middleware
SecurityHeadersMiddleware returns a Middleware that sets a conservative set of response security headers on every request:
- X-Content-Type-Options: nosniff
- X-Frame-Options: DENY
- Content-Security-Policy: frame-ancestors 'none'
- Referrer-Policy: no-referrer
HSTS and a full CSP are opt-in via WithHSTS and WithContentSecurityPolicy. HSTS is off by default because it is only meaningful over TLS.
Headers are set before the wrapped handler runs so a handler that writes its own response (and may call WriteHeader) still emits them. A handler is free to override any of these by setting its own value.
The built-in docs/openapi handlers apply this middleware by default; it is not forced onto user-supplied handlers.
type RateLimitConfig ¶ added in v0.23.0
type RateLimitConfig struct {
// RequestsPerSecond is the sustained fill rate of the token bucket.
// Must be > 0. Default: 50.
RequestsPerSecond float64 `mapstructure:"requests_per_second" yaml:"requests_per_second" json:"requests_per_second"`
// Burst is the bucket capacity — the maximum number of requests that may
// be admitted in an instantaneous spike. Must be >= 1. Default: 100.
Burst int `mapstructure:"burst" yaml:"burst" json:"burst"`
// KeyFunc derives the limiter key for a request, enabling per-client
// limiting. When nil, a single global bucket is used for all requests.
// A common choice is to key on the client IP (see ClientIPKey).
KeyFunc func(*http.Request) string `mapstructure:"-" yaml:"-" json:"-"`
// MaxTrackedKeys bounds the per-client bucket store. Ignored when KeyFunc
// is nil. Must be >= 1. Default: 8192.
MaxTrackedKeys int `mapstructure:"max_tracked_keys" yaml:"max_tracked_keys" json:"max_tracked_keys"`
// OnLimited is invoked when a request is rejected, before the 429 is
// written. Optional; useful for metrics/telemetry. A structured debug log
// is emitted via the constructor's logger regardless.
OnLimited func(*http.Request) `mapstructure:"-" yaml:"-" json:"-"`
}
RateLimitConfig configures the server-side token-bucket rate limiter.
func DefaultRateLimitConfig ¶ added in v0.23.0
func DefaultRateLimitConfig() RateLimitConfig
DefaultRateLimitConfig returns a RateLimitConfig suitable for a modest management/API server: 50 rps sustained, burst 100, single global bucket.
func MergeRateLimitConfig ¶ added in v0.30.0
func MergeRateLimitConfig(base, override RateLimitConfig, fields RateLimitConfigOverrides) RateLimitConfig
MergeRateLimitConfig applies explicitly supplied typed override values to base while leaving code-only function fields under caller control.
func RateLimitConfigFromConfig ¶ added in v0.23.0
func RateLimitConfigFromConfig(cfg config.Containable, prefix string) RateLimitConfig
RateLimitConfigFromConfig builds a RateLimitConfig from the config layer under "<prefix>.ratelimit.*" (prefix defaults to "server.http"), so operators tune the limiter via config like they tune the port or TLS.
Unset keys keep their DefaultRateLimitConfig values. The code-only fields (KeyFunc, OnLimited) are never read from config; wiring stays explicit.
type RateLimitConfigOverrides ¶ added in v0.30.0
RateLimitConfigOverrides records which typed rate limit config fields were explicitly supplied by an adapter.
type RegisterOption ¶
type RegisterOption func(*registerConfig)
RegisterOption configures registration-only behaviour for an HTTP server (middleware chain, request-body limit). Server construction settings — port, prefix, timeouts — are ServerOption values; Register accepts both families.
func WithMaxRequestBodyBytes ¶
func WithMaxRequestBodyBytes(n int64) RegisterOption
WithMaxRequestBodyBytes overrides the DefaultMaxRequestBodyBytes cap applied to every request body. Set to a negative value to disable the cap entirely (not recommended).
func WithMiddleware ¶
func WithMiddleware(chain Chain) RegisterOption
WithMiddleware sets the middleware chain applied to the handler before it is passed to the HTTP server. Health endpoints (/healthz, /livez, /readyz) are mounted outside the chain and are never affected by middleware.
type RetryConfig ¶
type RetryConfig struct {
// MaxRetries is the maximum number of retry attempts. Zero means no retries.
MaxRetries int
// InitialBackoff is the base delay before the first retry. Default: 500ms.
InitialBackoff time.Duration
// MaxBackoff caps the computed delay. Default: 30s.
MaxBackoff time.Duration
// RetryableStatusCodes defines which HTTP status codes trigger a retry.
// Default: []int{429, 502, 503, 504}.
RetryableStatusCodes []int
// ShouldRetry is an optional custom predicate. When set, it replaces the
// default status-code and network-error checks. The attempt count (0-based)
// and either the response or the transport error are provided.
ShouldRetry func(attempt int, resp *http.Response, err error) bool
}
RetryConfig configures the retry behaviour of the HTTP client.
func DefaultRetryConfig ¶
func DefaultRetryConfig() RetryConfig
DefaultRetryConfig returns a RetryConfig suitable for most use cases.
type SecurityHeadersOption ¶ added in v0.17.0
type SecurityHeadersOption func(*securityHeadersConfig)
SecurityHeadersOption configures SecurityHeadersMiddleware.
func WithContentSecurityPolicy ¶ added in v0.17.0
func WithContentSecurityPolicy(policy string) SecurityHeadersOption
WithContentSecurityPolicy sets a full Content-Security-Policy header value, replacing the conservative default ("frame-ancestors 'none'"). The caller owns the complete policy when this option is supplied. An empty value falls back to the frame-ancestors-only default so the clickjacking control is never silently dropped.
func WithContentTypeOptions ¶ added in v0.17.0
func WithContentTypeOptions(value string) SecurityHeadersOption
WithContentTypeOptions overrides the X-Content-Type-Options header value (default "nosniff"). An empty value omits the header (not recommended).
func WithFrameOptions ¶ added in v0.17.0
func WithFrameOptions(value string) SecurityHeadersOption
WithFrameOptions overrides the X-Frame-Options header value (default "DENY"). An empty value omits the header; the Content-Security-Policy frame-ancestors directive is unaffected.
func WithHSTS ¶ added in v0.17.0
func WithHSTS(maxAge time.Duration, includeSubdomains, preload bool) SecurityHeadersOption
WithHSTS enables Strict-Transport-Security with the given max-age. HSTS is OFF by default because it is only meaningful over TLS; enable it only on a server that is exclusively reachable over HTTPS. A non-positive maxAge leaves HSTS disabled.
func WithReferrerPolicy ¶ added in v0.17.0
func WithReferrerPolicy(policy string) SecurityHeadersOption
WithReferrerPolicy overrides the Referrer-Policy header value (default "no-referrer"). An empty value omits the header.
type ServerOption ¶ added in v0.9.0
type ServerOption func(*serverConfig)
ServerOption configures an HTTP server built by NewServer or started by Start. ServerOption values are also accepted by Register.
func WithConfigPrefix ¶ added in v0.6.0
func WithConfigPrefix(prefix string) ServerOption
WithConfigPrefix sets the config prefix the server reads its port, TLS and max_header_bytes from (default "server.http"). Use it to run a second HTTP server on its own config block, e.g. "server.admin" for an internal server.
When constructing a server outside Register, pass the SAME prefix to both NewServer and Start so the listen port and TLS settings stay consistent.
func WithIdleTimeout ¶ added in v0.9.0
func WithIdleTimeout(d time.Duration) ServerOption
WithIdleTimeout overrides the built-in http.Server IdleTimeout.
func WithMaxHeaderBytes ¶ added in v0.9.0
func WithMaxHeaderBytes(n int) ServerOption
WithMaxHeaderBytes overrides <prefix>.max_header_bytes and the built-in 1 MB default for the constructed server's MaxHeaderBytes.
func WithPort ¶ added in v0.9.0
func WithPort(port int) ServerOption
WithPort sets the listen port explicitly, bypassing config lookup entirely. It has the highest precedence: it overrides both <prefix>.port and the server.port shared fallback.
func WithReadTimeout ¶ added in v0.9.0
func WithReadTimeout(d time.Duration) ServerOption
WithReadTimeout overrides the built-in http.Server ReadTimeout.
func WithServerTLSConfig ¶ added in v0.9.0
func WithServerTLSConfig(c *tls.Config) ServerOption
WithServerTLSConfig replaces the default hardened *tls.Config on the constructed server. Cert/key resolution for serving still flows through Start (from the server's TLS config prefix). It is named distinctly from the client-side WithTLSConfig option in this package.
func WithWriteTimeout ¶ added in v0.9.0
func WithWriteTimeout(d time.Duration) ServerOption
WithWriteTimeout overrides the built-in http.Server WriteTimeout.
type ServerSettings ¶ added in v0.30.0
type ServerSettings struct {
Port int `mapstructure:"port" yaml:"port" json:"port"`
MaxHeaderBytes int `mapstructure:"max_header_bytes" yaml:"max_header_bytes" json:"max_header_bytes"`
}
ServerSettings contains the data needed to construct an HTTP server without binding the core constructor to any particular config system.
func ServerSettingsFromConfig ¶ added in v0.30.0
func ServerSettingsFromConfig(cfg config.Containable, prefix string) ServerSettings
ServerSettingsFromConfig resolves HTTP server settings from GTB config. It preserves the existing fallback from <prefix>.port to server.port and keeps max_header_bytes scoped to the selected prefix.
type ServerSettingsSource ¶ added in v0.30.0
type ServerSettingsSource interface {
Current() *ServerSettings
Version() uint64
}
ServerSettingsSource exposes the latest HTTP server settings snapshot to packages that need reload-aware access without depending on GTB config.