Documentation
¶
Overview ¶
Package httpserver provides a production-ready HTTP server with graceful shutdown, observability, and middleware support.
Quick Start ¶
Create a server with your handler and start it:
mux := http.NewServeMux()
mux.HandleFunc("/", homeHandler)
server := httpserver.New(
httpserver.WithServiceName("my-api"),
httpserver.WithHandler(mux),
)
if err := server.ListenAndServe(ctx); err != nil {
log.Fatal(err)
}
ServiceName Integration ¶
The server's ServiceName is automatically propagated to all observability components. Set it once and it appears everywhere:
server := httpserver.New(
httpserver.WithServiceName("payment-api"), // Set once
httpserver.WithTracing(httpserver.TracingConfig{}), // Gets ServiceName
httpserver.WithMetrics(httpserver.MetricsConfig{}), // Gets ServiceName
httpserver.WithLogging(httpserver.LoggerConfig{...}), // Gets ServiceName
httpserver.WithHealth(&health, "1.0.0"), // Gets ServiceName
httpserver.WithHandler(mux),
)
Configuration ¶
Use preset configurations as a starting point:
// For production with hardened timeouts
server := httpserver.New(
httpserver.WithConfig(httpserver.ProductionConfig()),
httpserver.WithServiceName("payment-api"),
httpserver.WithHandler(mux),
)
Rate Limiting ¶
Global rate limiting (all requests):
server := httpserver.New(
httpserver.WithRateLimit(httpserver.RateLimitConfig{
Limit: 100, // 100 req/sec
Burst: 200,
}),
httpserver.WithHandler(mux),
)
Per-endpoint rate limiting (apply middleware to specific routes):
// Stricter limit for login
mux.Handle("/api/login", httpserver.RateLimitByIP(10, 20)(loginHandler))
// Different limit for API
mux.Handle("/api/", httpserver.RateLimit(httpserver.RateLimitConfig{
Limit: 50,
Burst: 100,
})(apiHandler))
Distributed rate limiting with Redis:
rdb := redis.NewUniversalClient(&redis.UniversalOptions{
Addrs: []string{"localhost:6379"},
})
mux.Handle("/api/", httpserver.RateLimitByIPRedis(rdb, 100, 200)(apiHandler))
Health Checks ¶
Register health endpoints with auto-configured ServiceName:
var health *httpserver.HealthHandler
server := httpserver.New(
httpserver.WithServiceName("my-api"),
httpserver.WithHealth(&health, "1.0.0"),
httpserver.WithHandler(mux),
)
health.AddReadinessCheck("database", dbPingCheck)
health.AddReadinessCheck("redis", redisPingCheck)
mux.Handle("/ping", health.PingHandler())
mux.Handle("/livez", health.LiveHandler())
mux.Handle("/readyz", health.ReadyHandler())
Framework Adapters ¶
Use adapters for popular frameworks:
import "github.com/kroma-labs/sentinel-go/httpserver/adapters/chi" import "github.com/kroma-labs/sentinel-go/httpserver/adapters/gin" import "github.com/kroma-labs/sentinel-go/httpserver/adapters/echo" import "github.com/kroma-labs/sentinel-go/httpserver/adapters/fiber" import "github.com/kroma-labs/sentinel-go/httpserver/adapters/grpcgateway"
Index ¶
- Constants
- Variables
- func ClientIDFromContext(ctx context.Context) string
- func PprofHandler(cfg PprofConfig) http.Handler
- func PrometheusHandler() http.Handler
- func PrometheusHandlerFor(opts promhttp.HandlerOpts) http.Handler
- func RegisterPprof(mux *http.ServeMux, cfg PprofConfig)
- func RequestIDFromContext(ctx context.Context) string
- func WriteError(w http.ResponseWriter, statusCode int, message string, errors ...Error)
- func WriteJSON[T any](w http.ResponseWriter, statusCode int, response Response[T])
- func WriteSuccess[T any](w http.ResponseWriter, statusCode int, data T, message string)
- type CORSConfig
- type CheckResult
- type Config
- type CredentialValidator
- type CredentialValidatorFunc
- type Error
- type HealthCheck
- type HealthHandler
- func (h *HealthHandler) AddLivenessCheck(name string, check HealthCheck)
- func (h *HealthHandler) AddReadinessCheck(name string, check HealthCheck)
- func (h *HealthHandler) LiveHandler() http.Handler
- func (h *HealthHandler) PingHandler() http.Handler
- func (h *HealthHandler) ReadyHandler() http.Handler
- type HealthOption
- type HealthResponse
- type KeyFunc
- type Limiter
- type LoggerConfig
- type MemoryCredentialValidator
- type Metrics
- type MetricsConfig
- type Middleware
- func CORS(cfg CORSConfig) Middleware
- func Chain(middlewares ...Middleware) Middleware
- func DefaultMiddleware(opts ...MiddlewareOption) Middleware
- func Logger(cfg LoggerConfig) Middleware
- func RateLimit(cfg RateLimitConfig) Middleware
- func RateLimitByIP(limit rate.Limit, burst int) Middleware
- func RateLimitByIPRedis(rdb redis.UniversalClient, limit rate.Limit, burst int) Middleware
- func Recovery(logger zerolog.Logger) Middleware
- func RequestID() Middleware
- func ServiceAuth(cfg ServiceAuthConfig) Middleware
- func Timeout(timeout time.Duration) Middleware
- func Tracing(cfg TracingConfig) Middleware
- type MiddlewareOption
- type Option
- func WithConfig(cfg Config) Option
- func WithHandler(h http.Handler) Option
- func WithHealth(handler **HealthHandler, version string) Option
- func WithLogger(l zerolog.Logger) Option
- func WithLogging(cfg LoggerConfig) Option
- func WithMetrics(cfg MetricsConfig) Option
- func WithMiddleware(ms ...Middleware) Option
- func WithRateLimit(cfg RateLimitConfig) Option
- func WithServiceName(name string) Option
- func WithTracing(cfg TracingConfig) Option
- type PingResponse
- type PprofConfig
- type RateLimitConfig
- type Response
- type SQLCredentialValidator
- type Server
- type ServiceAuthConfig
- type TracingConfig
Constants ¶
const RequestIDHeader = "X-Request-ID"
RequestIDHeader is the header key for request IDs.
Variables ¶
var ErrInvalidCredentials = errors.New("invalid credentials")
ErrInvalidCredentials is returned when authentication fails. Intentionally generic to not reveal which part of credentials is wrong.
Functions ¶
func ClientIDFromContext ¶
ClientIDFromContext returns the client_id from the request context. Returns empty string if not authenticated via ServiceAuth.
func PprofHandler ¶
func PprofHandler(cfg PprofConfig) http.Handler
PprofHandler returns an http.Handler that serves pprof endpoints.
Available endpoints:
- /debug/pprof/ - Index page
- /debug/pprof/cmdline - Command line
- /debug/pprof/profile - CPU profile
- /debug/pprof/symbol - Symbol lookup
- /debug/pprof/trace - Execution trace
- /debug/pprof/heap - Heap profile
- /debug/pprof/goroutine - Goroutine profile
- /debug/pprof/block - Block profile
- /debug/pprof/mutex - Mutex profile
- /debug/pprof/allocs - Allocation profile
- /debug/pprof/threadcreate - Thread creation profile
Example:
mux.Handle("/debug/pprof/", httpserver.PprofHandler(httpserver.PprofConfig{
EnableAuth: true,
Username: "admin",
Password: "secret",
}))
func PrometheusHandler ¶
PrometheusHandler returns an http.Handler for the /metrics endpoint.
This exposes Prometheus metrics in the standard text format.
Example:
mux.Handle("/metrics", httpserver.PrometheusHandler())
func PrometheusHandlerFor ¶
func PrometheusHandlerFor(opts promhttp.HandlerOpts) http.Handler
PrometheusHandlerFor returns a Prometheus handler with custom options.
Example:
mux.Handle("/metrics", httpserver.PrometheusHandlerFor(opts))
func RegisterPprof ¶
func RegisterPprof(mux *http.ServeMux, cfg PprofConfig)
RegisterPprof registers pprof handlers on the given ServeMux.
Example:
mux := http.NewServeMux() httpserver.RegisterPprof(mux, httpserver.DefaultPprofConfig())
func RequestIDFromContext ¶
RequestIDFromContext extracts the request ID from the context.
Returns an empty string if no request ID is present.
Example:
func myHandler(w http.ResponseWriter, r *http.Request) {
id := httpserver.RequestIDFromContext(r.Context())
log.Printf("Processing request: %s", id)
}
func WriteError ¶
func WriteError(w http.ResponseWriter, statusCode int, message string, errors ...Error)
WriteError writes a JSON error response.
Example:
httpserver.WriteError(w, http.StatusBadRequest,
"validation failed",
httpserver.Error{Field: "email", Message: "invalid format"},
)
func WriteJSON ¶
func WriteJSON[T any](w http.ResponseWriter, statusCode int, response Response[T])
WriteJSON writes a JSON response with the given status code.
If JSON encoding fails, the error is logged but not returned since HTTP headers have already been written at that point.
Example:
type UserData struct {
ID int `json:"id"`
Name string `json:"name"`
}
httpserver.WriteJSON(w, http.StatusOK, Response[UserData]{
Data: UserData{ID: 1, Name: "John"},
Message: "success",
})
func WriteSuccess ¶
func WriteSuccess[T any](w http.ResponseWriter, statusCode int, data T, message string)
WriteSuccess writes a success JSON response with data.
Example:
httpserver.WriteSuccess(w, http.StatusOK, userData, "user retrieved")
Types ¶
type CORSConfig ¶
type CORSConfig struct {
// AllowedOrigins is a list of origins that are allowed.
// Use "*" to allow all origins (not recommended for production).
AllowedOrigins []string
// AllowedMethods is a list of HTTP methods allowed.
// Default: GET, POST, PUT, DELETE, OPTIONS, HEAD, PATCH
AllowedMethods []string
// AllowedHeaders is a list of headers that are allowed in requests.
AllowedHeaders []string
// ExposedHeaders is a list of headers that are exposed to the client.
ExposedHeaders []string
// AllowCredentials indicates whether credentials (cookies, auth headers)
// are allowed in cross-origin requests.
AllowCredentials bool
// MaxAge is the maximum age (in seconds) of the preflight cache.
// Default: 86400 (24 hours)
MaxAge int
}
CORSConfig configures the CORS middleware.
func DefaultCORSConfig ¶
func DefaultCORSConfig() CORSConfig
DefaultCORSConfig returns a permissive CORS configuration.
This is suitable for development. For production, customize the AllowedOrigins to your specific domains.
type CheckResult ¶
type CheckResult struct {
Status string `json:"status"`
Latency string `json:"latency"`
Message string `json:"message,omitempty"`
LastChecked string `json:"last_checked"`
ConsecutiveSuccess int `json:"consecutive_successes,omitempty"`
ConsecutiveFailures int `json:"consecutive_failures,omitempty"`
}
CheckResult contains the result of a single health check.
type Config ¶
type Config struct {
// Addr is the TCP address to listen on (default: ":8080").
Addr string
// ServiceName is the name of the service (e.g. "my-service").
// This is used for metrics, tracing, and health checks.
// Default: "http-server"
ServiceName string
// ReadTimeout is the maximum duration for reading the entire request,
// including the body. A zero or negative value means no timeout.
//
// Setting this helps protect against slow-loris attacks where a client
// sends data very slowly to hold connections open.
//
// Default: 15s
ReadTimeout time.Duration
// ReadHeaderTimeout is the maximum duration for reading request headers.
// If zero, ReadTimeout is used. If both are zero, there is no timeout.
//
// Default: 10s
ReadHeaderTimeout time.Duration
// WriteTimeout is the maximum duration before timing out writes of the response.
// A zero or negative value means no timeout.
//
// Default: 15s
WriteTimeout time.Duration
// IdleTimeout is the maximum duration to wait for the next request when
// keep-alives are enabled. If zero, ReadTimeout is used.
//
// Default: 60s
IdleTimeout time.Duration
// MaxHeaderBytes controls the maximum number of bytes the server will
// read parsing the request header's keys and values.
//
// Default: 1MB (1 << 20)
MaxHeaderBytes int
// TLSConfig optionally provides TLS configuration for HTTPS.
// If nil, the server runs in HTTP mode.
TLSConfig *tls.Config
// Logger configures the server logger.
Logger zerolog.Logger
// Middleware is a list of middleware to apply to the server.
Middleware []Middleware
// Handler is the HTTP handler to serve requests.
// This is required and must be set via WithHandler().
Handler http.Handler
// ShutdownTimeout is the maximum duration to wait for active connections
// to complete during graceful shutdown.
//
// When shutdown is triggered:
// 1. Server stops accepting new connections
// 2. Waits up to ShutdownTimeout for in-flight requests to complete
// 3. Forcibly closes remaining connections
//
// Default: 10s
ShutdownTimeout time.Duration
// DrainInterval is the interval between checks during shutdown to see if
// all connections have been drained.
//
// Default: 500ms
DrainInterval time.Duration
// TracingConfig enables tracing. If set, ServiceName is automatically applied.
TracingConfig *TracingConfig
// MetricsConfig enables metrics. If set, ServiceName is automatically applied.
MetricsConfig *MetricsConfig
// LoggerConfig enables request logging. If set, ServiceName is automatically applied.
LoggerConfig *LoggerConfig
// HealthHandler is populated by WithHealth with the server's ServiceName.
HealthHandler **HealthHandler
// HealthVersion is the version string for health responses.
HealthVersion string
// RateLimitConfig enables global rate limiting.
RateLimitConfig *RateLimitConfig
}
Config holds the HTTP server configuration parameters.
Use DefaultConfig(), ProductionConfig(), or DevelopmentConfig() to get a properly initialized configuration, then modify specific fields as needed.
Example:
cfg := httpserver.DefaultConfig()
cfg.Addr = ":9090"
cfg.ShutdownTimeout = 15 * time.Second
server := httpserver.New(
httpserver.WithConfig(cfg),
httpserver.WithHandler(mux),
)
func DefaultConfig ¶
func DefaultConfig() Config
DefaultConfig returns a balanced configuration suitable for most use cases.
This configuration provides reasonable timeouts that protect against common attacks while being lenient enough for typical web applications.
Timeout values:
- ReadTimeout: 15s
- WriteTimeout: 15s
- IdleTimeout: 60s
- ShutdownTimeout: 10s
func DevelopmentConfig ¶
func DevelopmentConfig() Config
DevelopmentConfig returns a lenient configuration for local development.
Key differences from DefaultConfig:
- No read/write timeouts (allows debugging with breakpoints)
- Longer idle timeout (less connection churn during development)
- Very short shutdown timeout (fast restart during iteration)
Warning: Do not use this in production!
Timeout values:
- ReadTimeout: 0 (unlimited)
- WriteTimeout: 0 (unlimited)
- IdleTimeout: 120s
- ShutdownTimeout: 3s
func ProductionConfig ¶
func ProductionConfig() Config
ProductionConfig returns a hardened configuration optimized for production.
Designed for Kubernetes environments where the default terminationGracePeriodSeconds is 30s.
Rationale:
- ReadTimeout (10s): Most API requests complete in <1s; 10s catches slow clients while failing fast for genuinely stalled connections.
- WriteTimeout (10s): Matches read; responses should complete quickly.
- IdleTimeout (30s): Shorter than default to free resources faster; most HTTP/2 clients handle reconnection gracefully.
- ShutdownTimeout (25s): K8s sends SIGTERM, then waits terminationGracePeriodSeconds (30s) before SIGKILL. We use 25s to complete gracefully with 5s buffer. This allows in-flight requests to finish while ensuring we exit before K8s forcibly kills the pod.
Timeout values:
- ReadTimeout: 10s
- WriteTimeout: 10s
- IdleTimeout: 30s
- ShutdownTimeout: 25s (5s buffer before K8s SIGKILL at 30s)
type CredentialValidator ¶
type CredentialValidator interface {
// Validate checks if the client_id and passkey are valid.
// Returns nil if valid, error if invalid.
Validate(ctx context.Context, clientID, passkey string) error
}
CredentialValidator validates service credentials.
type CredentialValidatorFunc ¶
CredentialValidatorFunc is an adapter to allow ordinary functions as validators.
type HealthCheck ¶
HealthCheck is a function that checks the health of a dependency.
Return nil if the dependency is healthy, or an error describing the issue.
Example:
func dbHealthCheck(ctx context.Context) error {
return db.PingContext(ctx)
}
type HealthHandler ¶
type HealthHandler struct {
// contains filtered or unexported fields
}
HealthHandler manages health check endpoints.
Create a HealthHandler using NewHealthHandler():
health := httpserver.NewHealthHandler(
httpserver.WithServiceName("my-service"),
httpserver.WithVersion("1.0.0"),
)
health.AddReadinessCheck("postgres", dbChecker)
health.AddReadinessCheck("redis", redisChecker)
mux.Handle("/ping", health.PingHandler())
mux.Handle("/livez", health.LiveHandler())
mux.Handle("/readyz", health.ReadyHandler())
func NewHealthHandler ¶
func NewHealthHandler(opts ...HealthOption) *HealthHandler
NewHealthHandler creates a new HealthHandler.
When using with httpserver, use WithHealth instead for automatic ServiceName.
Example (standalone):
health := httpserver.NewHealthHandler(
httpserver.WithVersion("2.1.0"),
)
Example (with server - recommended):
var health *httpserver.HealthHandler
server := httpserver.New(
httpserver.WithServiceName("my-api"),
httpserver.WithHealth(&health, "2.1.0"),
)
// health is now usable with ServiceName auto-set
func (*HealthHandler) AddLivenessCheck ¶
func (h *HealthHandler) AddLivenessCheck(name string, check HealthCheck)
AddLivenessCheck adds a health check for the liveness probe.
Liveness checks determine if the process is alive and not deadlocked. If a liveness check fails, Kubernetes will restart the pod.
Use sparingly - most applications only need basic process checks.
Example:
health.AddLivenessCheck("goroutines", func(ctx context.Context) error {
if runtime.NumGoroutine() > 10000 {
return errors.New("too many goroutines")
}
return nil
})
func (*HealthHandler) AddReadinessCheck ¶
func (h *HealthHandler) AddReadinessCheck(name string, check HealthCheck)
AddReadinessCheck adds a health check for the readiness probe.
Readiness checks determine if the service can handle traffic. If a readiness check fails, Kubernetes stops routing traffic to the pod but does not restart it.
Add checks for all critical dependencies (database, cache, message queues).
Example:
health.AddReadinessCheck("postgres", func(ctx context.Context) error {
return db.PingContext(ctx)
})
func (*HealthHandler) LiveHandler ¶
func (h *HealthHandler) LiveHandler() http.Handler
LiveHandler returns an http.Handler for the /livez endpoint.
Returns 200 if all liveness checks pass, 503 otherwise.
func (*HealthHandler) PingHandler ¶
func (h *HealthHandler) PingHandler() http.Handler
PingHandler returns an http.Handler for the /ping endpoint.
This is a simple connectivity check that always returns 200 OK. It does not run any health checks.
func (*HealthHandler) ReadyHandler ¶
func (h *HealthHandler) ReadyHandler() http.Handler
ReadyHandler returns an http.Handler for the /readyz endpoint.
Returns 200 if all readiness checks pass, 503 otherwise.
type HealthOption ¶
type HealthOption func(*HealthHandler)
HealthOption configures the HealthHandler.
func WithVersion ¶
func WithVersion(version string) HealthOption
WithVersion sets the version for health responses.
type HealthResponse ¶
type HealthResponse struct {
Status string `json:"status"`
Service string `json:"service"`
Version string `json:"version"`
Uptime string `json:"uptime,omitempty"`
Hostname string `json:"hostname,omitempty"`
Timestamp string `json:"timestamp"`
Checks map[string]CheckResult `json:"checks,omitempty"`
}
HealthResponse contains the full health response data.
type KeyFunc ¶
KeyFunc extracts a rate limiting key from a request.
The key determines how requests are grouped for rate limiting. Requests with the same key share the same rate limit bucket.
Example Usage ¶
Global rate limit (all requests share one bucket):
httpserver.RateLimit(httpserver.RateLimitConfig{
Limit: 100, // 100 requests per second
Burst: 200, // Allow bursts up to 200
KeyFunc: nil, // nil = global (all requests share bucket)
})
Per-IP rate limit (each IP has its own bucket):
httpserver.RateLimit(httpserver.RateLimitConfig{
Limit: 100,
Burst: 200,
KeyFunc: httpserver.KeyFuncByIP(), // Each IP gets 100 req/sec
})
Per-endpoint rate limit (each endpoint has its own bucket):
httpserver.RateLimit(httpserver.RateLimitConfig{
Limit: 100,
Burst: 200,
KeyFunc: httpserver.KeyFuncByPath(), // /api/users and /api/orders limited separately
})
Per-IP per-endpoint rate limit (most granular):
httpserver.RateLimit(httpserver.RateLimitConfig{
Limit: 100,
Burst: 200,
KeyFunc: httpserver.KeyFuncByIPAndPath(), // IP1:/api/users != IP1:/api/orders != IP2:/api/users
})
func KeyFuncByClientID ¶
func KeyFuncByClientID() KeyFunc
KeyFuncByClientID returns a KeyFunc that uses the client_id from ServiceAuth.
Use Case ¶
Rate limit authenticated service clients. Use after ServiceAuth middleware.
Example ¶
// Each authenticated client gets 1000 req/sec across all endpoints
mux.Handle("/internal/", httpserver.Chain(
httpserver.ServiceAuth(authConfig),
httpserver.RateLimit(httpserver.RateLimitConfig{
Limit: 1000,
Burst: 2000,
KeyFunc: httpserver.KeyFuncByClientID(),
}),
)(handler))
Prerequisite ¶
Must be used after ServiceAuth middleware. Without authentication, returns empty string (all unauthenticated requests share one bucket).
func KeyFuncByClientIDAndPath ¶
func KeyFuncByClientIDAndPath() KeyFunc
KeyFuncByClientIDAndPath combines client_id and path.
Use Case ¶
Rate limit authenticated clients per endpoint. For example, a client might have different limits for read vs write operations.
Example ¶
// client-1 gets 100 req/sec to /api/read
// client-1 gets separate 10 req/sec to /api/write
mux.Handle("/api/", httpserver.Chain(
httpserver.ServiceAuth(authConfig),
httpserver.RateLimit(httpserver.RateLimitConfig{
Limit: 100,
Burst: 200,
KeyFunc: httpserver.KeyFuncByClientIDAndPath(),
}),
)(handler))
func KeyFuncByHeader ¶
KeyFuncByHeader returns a KeyFunc that extracts a value from a header.
Use Case ¶
Rate limit by a custom header, such as tenant ID or API key.
Example ¶
// Each tenant gets 100 req/sec
mux.Handle("/api/", httpserver.RateLimit(httpserver.RateLimitConfig{
Limit: 100,
Burst: 200,
KeyFunc: httpserver.KeyFuncByHeader("X-Tenant-ID"),
})(handler))
func KeyFuncByIP ¶
func KeyFuncByIP() KeyFunc
KeyFuncByIP returns a KeyFunc that extracts the client IP.
Uses X-Forwarded-For header if present (for reverse proxy setups), otherwise falls back to RemoteAddr.
Use Case ¶
Rate limit each client IP independently. For example, 100 req/sec per IP means one abusive client doesn't affect other clients.
Example ¶
// Each IP can make 100 requests per second
mux.Handle("/api/", httpserver.RateLimit(httpserver.RateLimitConfig{
Limit: 100,
Burst: 200,
KeyFunc: httpserver.KeyFuncByIP(),
})(handler))
func KeyFuncByIPAndPath ¶
func KeyFuncByIPAndPath() KeyFunc
KeyFuncByIPAndPath returns a KeyFunc that combines client IP and path.
Use Case ¶
Most granular rate limiting for public APIs. Each client IP gets its own rate limit bucket per endpoint.
Example ¶
// IP 1.2.3.4 gets 100 req/sec to /api/users
// IP 1.2.3.4 gets separate 100 req/sec to /api/orders
// IP 5.6.7.8 gets its own 100 req/sec to /api/users
mux.Handle("/api/", httpserver.RateLimit(httpserver.RateLimitConfig{
Limit: 100,
Burst: 200,
KeyFunc: httpserver.KeyFuncByIPAndPath(),
})(handler))
func KeyFuncByPath ¶
func KeyFuncByPath() KeyFunc
KeyFuncByPath returns a KeyFunc that uses the URL path.
Use Case ¶
Rate limit each endpoint independently. For example, /api/search might have a lower limit than /api/users because it's more expensive.
Example ¶
// All requests to /api/search share 10 req/sec
// All requests to /api/users share separate 100 req/sec
mux.Handle("/api/", httpserver.RateLimit(httpserver.RateLimitConfig{
Limit: 100,
Burst: 200,
KeyFunc: httpserver.KeyFuncByPath(),
})(handler))
Note ¶
This limits ALL clients combined per endpoint. For per-client per-endpoint, use KeyFuncByIPAndPath or KeyFuncByClientIDAndPath.
type LoggerConfig ¶
type LoggerConfig struct {
Logger zerolog.Logger
// SkipPaths are paths that should not be logged.
// Useful for health check endpoints that are called frequently.
SkipPaths []string
// LogRequestBody enables logging of request body (use with caution).
// This reads the entire request body into memory, which may impact
// performance for large payloads. Consider using only in development.
LogRequestBody bool
// LogResponseBody enables logging of response body (use with caution).
// This buffers the entire response body, which may impact performance
// for large payloads. Consider using only in development.
LogResponseBody bool
// MaxBodyLogSize limits the size of logged bodies (default: 4KB).
// Bodies larger than this will be truncated in the log output.
MaxBodyLogSize int
// contains filtered or unexported fields
}
LoggerConfig configures the logging middleware.
type MemoryCredentialValidator ¶
type MemoryCredentialValidator struct {
// contains filtered or unexported fields
}
MemoryCredentialValidator validates against an in-memory map. Useful for credentials loaded from environment variables.
func NewMemoryCredentialValidator ¶
func NewMemoryCredentialValidator(clients map[string]string) *MemoryCredentialValidator
NewMemoryCredentialValidator creates a validator from a map of client_id -> passkey.
Example:
validator := httpserver.NewMemoryCredentialValidator(map[string]string{
os.Getenv("SERVICE_CLIENT_ID"): os.Getenv("SERVICE_PASS_KEY"),
})
type Metrics ¶
type Metrics struct {
// contains filtered or unexported fields
}
Metrics provides server metrics using OpenTelemetry.
func NewMetrics ¶
func NewMetrics(cfg MetricsConfig) (*Metrics, error)
NewMetrics creates a new Metrics instance.
func (*Metrics) Middleware ¶
func (m *Metrics) Middleware() Middleware
Middleware returns middleware that records HTTP metrics.
Metrics recorded:
- http.server.request.duration: Request latency histogram
- http.server.request.size: Request body size histogram
- http.server.response.size: Response body size histogram
- http.server.active_requests: In-flight request gauge
- http.server.request.total: Total request counter
- http.server.response.status: Status code distribution
Example:
metrics, _ := httpserver.NewMetrics(httpserver.DefaultMetricsConfig()) handler := metrics.Middleware()(myHandler)
type MetricsConfig ¶
type MetricsConfig struct {
// MeterProvider is the OTel meter provider.
// If nil, uses otel.GetMeterProvider().
MeterProvider metric.MeterProvider
// SkipPaths are paths that should not be recorded.
SkipPaths []string
// Buckets for request duration histogram (in seconds).
// Default: [0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10]
DurationBuckets []float64
// contains filtered or unexported fields
}
MetricsConfig configures the metrics middleware.
func DefaultMetricsConfig ¶
func DefaultMetricsConfig() MetricsConfig
DefaultMetricsConfig returns a default metrics configuration.
type Middleware ¶
Middleware is a function that wraps an http.Handler.
Middleware functions are composed together using Chain() to create a processing pipeline for HTTP requests.
Example:
func LoggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("Request: %s %s", r.Method, r.URL.Path)
next.ServeHTTP(w, r)
})
}
func CORS ¶
func CORS(cfg CORSConfig) Middleware
CORS returns middleware that handles Cross-Origin Resource Sharing.
Example:
handler := httpserver.CORS(httpserver.CORSConfig{
AllowedOrigins: []string{"https://example.com"},
AllowCredentials: true,
})(myHandler)
func Chain ¶
func Chain(middlewares ...Middleware) Middleware
Chain composes multiple middleware into a single middleware.
Middleware are applied in the order provided. The first middleware is the outermost (runs first on request, last on response).
Example:
handler := httpserver.Chain(
httpserver.Tracing(tp),
httpserver.Recovery(),
httpserver.Logger(logger),
)(myHandler)
Request flow:
Tracing -> Recovery -> Logger -> myHandler -> Logger -> Recovery -> Tracing
func DefaultMiddleware ¶
func DefaultMiddleware(opts ...MiddlewareOption) Middleware
DefaultMiddleware returns a production-ready middleware stack.
The stack includes (in order):
- Recovery - Panic recovery
- RequestID - X-Request-ID generation/forwarding
- Logger - Request/response logging (if logger provided)
For full observability, add Tracing() middleware manually with your TracerProvider.
Example:
handler := httpserver.Tracing(tp)(
httpserver.DefaultMiddleware(logger)(myHandler),
)
func Logger ¶
func Logger(cfg LoggerConfig) Middleware
Logger returns middleware that logs HTTP requests.
Logs include:
- Method, path, status code
- Request duration
- Request ID (if present)
- Client IP
- Request/Response bodies (if enabled)
Example:
handler := httpserver.Logger(httpserver.LoggerConfig{
Logger: logger,
SkipPaths: []string{"/livez", "/readyz", "/ping"},
})(myHandler)
func RateLimit ¶
func RateLimit(cfg RateLimitConfig) Middleware
RateLimit returns middleware that limits request rate using token bucket algorithm.
func RateLimitByIP ¶
func RateLimitByIP(limit rate.Limit, burst int) Middleware
RateLimitByIP returns rate limiting middleware keyed by client IP.
func RateLimitByIPRedis ¶
func RateLimitByIPRedis(rdb redis.UniversalClient, limit rate.Limit, burst int) Middleware
RateLimitByIPRedis returns distributed rate limiting middleware keyed by client IP.
func Recovery ¶
func Recovery(logger zerolog.Logger) Middleware
Recovery returns middleware that recovers from panics.
When a panic occurs:
- The panic is recovered
- A 500 Internal Server Error is returned
- The stack trace is logged (if logger provided)
- The request continues to the next middleware
Example:
handler := httpserver.Recovery(logger)(myHandler)
func RequestID ¶
func RequestID() Middleware
RequestID returns middleware that generates or forwards request IDs.
Behavior:
- If X-Request-ID header exists, use it
- Otherwise, generate a new UUID v4
- Add the ID to the response header
- Store the ID in the request context
Example:
handler := httpserver.RequestID()(myHandler)
// Access in handler:
func myHandler(w http.ResponseWriter, r *http.Request) {
id := httpserver.RequestIDFromContext(r.Context())
log.Printf("Request ID: %s", id)
}
func ServiceAuth ¶
func ServiceAuth(cfg ServiceAuthConfig) Middleware
ServiceAuth returns middleware that validates service-to-service credentials.
Requires both Client-ID and Pass-Key headers to be present and valid. On failure, returns 401 Unauthorized with a generic error message.
Example with in-memory validator:
validator := httpserver.NewMemoryCredentialValidator(map[string]string{
os.Getenv("CLIENT_ID"): os.Getenv("PASS_KEY"),
})
mux.Handle("/internal/", httpserver.ServiceAuth(httpserver.ServiceAuthConfig{
Validator: validator,
})(internalHandler))
Example with database validator:
validator := httpserver.NewSQLCredentialValidator(db,
"SELECT passkey FROM service_credentials WHERE client_id = $1 AND is_active = true",
)
mux.Handle("/internal/", httpserver.ServiceAuth(httpserver.ServiceAuthConfig{
Validator: validator,
})(internalHandler))
func Timeout ¶
func Timeout(timeout time.Duration) Middleware
Timeout returns middleware that limits request processing time.
If the handler takes longer than the timeout, the request context is cancelled and a 503 Service Unavailable response is returned.
Note: The handler must respect context cancellation for this to work effectively.
Example:
handler := httpserver.Timeout(30 * time.Second)(myHandler)
func Tracing ¶
func Tracing(cfg TracingConfig) Middleware
Tracing returns middleware that adds OpenTelemetry tracing to requests.
Features:
- Extracts trace context from incoming requests (W3C TraceContext)
- Creates server spans with standard HTTP attributes
- Records errors on 5xx responses
- Propagates span context to downstream handlers
Example:
handler := httpserver.Tracing(httpserver.TracingConfig{
TracerProvider: tp,
ServiceName: "api-gateway",
})(myHandler)
type MiddlewareOption ¶
type MiddlewareOption func(*middlewareConfig)
MiddlewareOption configures DefaultMiddleware.
func WithDefaultLogger ¶
func WithDefaultLogger(cfg LoggerConfig) MiddlewareOption
WithDefaultLogger adds logging middleware to DefaultMiddleware.
type Option ¶
type Option func(*Config)
Option configures the server.
func WithConfig ¶
WithConfig applies all settings from a Config struct.
This is the recommended way to configure the server. Use one of the preset configurations (DefaultConfig, ProductionConfig, DevelopmentConfig) as a starting point, then override specific fields as needed.
Example:
cfg := httpserver.ProductionConfig()
cfg.Addr = ":9090"
cfg.ShutdownTimeout = 30 * time.Second
server := httpserver.New(
httpserver.WithConfig(cfg),
httpserver.WithServiceName("my-api"),
httpserver.WithHandler(mux),
)
func WithHandler ¶
WithHandler sets the HTTP handler for the server.
This is required. The handler will be wrapped with any configured middleware in the order they are specified.
Example:
mux := http.NewServeMux()
mux.HandleFunc("/", homeHandler)
server := httpserver.New(
httpserver.WithHandler(mux),
)
func WithHealth ¶
func WithHealth(handler **HealthHandler, version string) Option
WithHealth enables health check endpoints with auto-configured ServiceName.
This creates a HealthHandler with the server's ServiceName and version, and returns it for adding checks and registering routes.
Example:
var health *httpserver.HealthHandler
server := httpserver.New(
httpserver.WithServiceName("my-api"),
httpserver.WithHealth(&health, "1.0.0"),
httpserver.WithHandler(mux),
)
health.AddReadinessCheck("database", dbPingCheck)
mux.Handle("/ping", health.PingHandler())
mux.Handle("/livez", health.LiveHandler())
mux.Handle("/readyz", health.ReadyHandler())
func WithLogger ¶
WithLogger sets the server logger for lifecycle events only.
This logger is used for:
- Server startup messages
- Graceful shutdown logging
- Internal errors
For REQUEST logging (each HTTP request/response), use WithLogging instead. You can use both: WithLogger for server events, WithLogging for requests.
Example:
logger := zerolog.New(os.Stdout).With().Timestamp().Logger()
server := httpserver.New(
httpserver.WithLogger(logger), // Server events
httpserver.WithLogging(httpserver.LoggerConfig{Logger: logger}), // Requests
httpserver.WithHandler(mux),
)
func WithLogging ¶
func WithLogging(cfg LoggerConfig) Option
WithLogging enables request logging middleware.
The server's ServiceName is automatically included in all log entries. You don't need to set ServiceName in LoggerConfig.
Example:
logger := zerolog.New(os.Stdout).With().Timestamp().Logger()
server := httpserver.New(
httpserver.WithServiceName("my-api"),
httpserver.WithLogging(httpserver.LoggerConfig{
Logger: logger,
SkipPaths: []string{"/livez", "/readyz", "/ping"},
}),
httpserver.WithHandler(mux),
)
func WithMetrics ¶
func WithMetrics(cfg MetricsConfig) Option
WithMetrics enables OpenTelemetry metrics middleware.
The server's ServiceName is automatically applied to all metrics. You don't need to set ServiceName in MetricsConfig.
Example:
server := httpserver.New(
httpserver.WithServiceName("my-api"),
httpserver.WithMetrics(httpserver.MetricsConfig{
SkipPaths: []string{"/livez", "/readyz", "/ping"},
}),
httpserver.WithHandler(mux),
)
func WithMiddleware ¶
func WithMiddleware(ms ...Middleware) Option
WithMiddleware adds middleware to wrap the handler.
Middleware is applied in order (first middleware wraps outermost). Use this for custom middleware or when you need fine-grained control.
For built-in middleware (tracing, metrics, logging), prefer the dedicated options (WithTracing, WithMetrics, WithLogging) which automatically inject the server's ServiceName.
Example:
server := httpserver.New(
httpserver.WithHandler(mux),
httpserver.WithMiddleware(
httpserver.Recovery(logger),
httpserver.RequestID(),
httpserver.CORS(corsConfig),
),
)
func WithRateLimit ¶
func WithRateLimit(cfg RateLimitConfig) Option
WithRateLimit enables global rate limiting for all requests.
For per-endpoint rate limiting, use the RateLimit middleware directly on specific routes instead.
Example (global rate limit):
server := httpserver.New(
httpserver.WithRateLimit(httpserver.RateLimitConfig{
Limit: 100, // 100 requests per second
Burst: 200, // Allow bursts up to 200
}),
httpserver.WithHandler(mux),
)
Example (per-endpoint rate limit - add middleware to specific routes):
// Apply stricter limit to sensitive endpoint
mux.Handle("/api/login", httpserver.RateLimitByIP(10, 20)(loginHandler))
// Apply different limit to API endpoints
mux.Handle("/api/", httpserver.RateLimit(httpserver.RateLimitConfig{
Limit: 50,
Burst: 100,
})(apiHandler))
func WithServiceName ¶
WithServiceName sets the service name for the entire server.
This is the SINGLE source of truth for service identity. The server automatically passes this value to all components that need it:
- Tracing spans (service.name attribute)
- Metrics (service.name label)
- Request logs (service field)
- Health check responses (service field)
Example:
server := httpserver.New(
httpserver.WithServiceName("payment-api"),
httpserver.WithHandler(mux),
)
func WithTracing ¶
func WithTracing(cfg TracingConfig) Option
WithTracing enables OpenTelemetry tracing middleware.
The server's ServiceName is automatically applied to all spans. You don't need to set ServiceName in TracingConfig.
Example:
server := httpserver.New(
httpserver.WithServiceName("my-api"),
httpserver.WithTracing(httpserver.TracingConfig{
SkipPaths: []string{"/livez", "/readyz", "/ping"},
}),
httpserver.WithHandler(mux),
)
type PingResponse ¶
type PingResponse struct {
Status string `json:"status"`
}
PingResponse contains the ping response data.
type PprofConfig ¶
type PprofConfig struct {
// Prefix is the URL prefix for pprof endpoints.
// Default: "/debug/pprof"
Prefix string
// EnableAuth enables basic authentication for pprof endpoints.
EnableAuth bool
// Username for basic auth (required when EnableAuth is true).
Username string
// Password for basic auth (required when EnableAuth is true).
Password string
}
PprofConfig configures pprof endpoint security.
func DefaultPprofConfig ¶
func DefaultPprofConfig() PprofConfig
DefaultPprofConfig returns default pprof configuration.
type RateLimitConfig ¶
type RateLimitConfig struct {
// Limit is the rate limit in requests per second.
Limit rate.Limit
// Burst is the maximum burst size (token bucket capacity).
Burst int
// KeyFunc extracts a key from the request for per-key rate limiting.
// If nil, a global rate limit is applied to all requests.
KeyFunc KeyFunc
// Redis enables distributed rate limiting across multiple instances.
// If nil, an in-memory rate limiter is used (single-instance only).
Redis redis.UniversalClient
// RedisKeyPrefix is the prefix for Redis keys.
// Default: "ratelimit:"
RedisKeyPrefix string
// WindowDuration is the sliding window duration for Redis rate limiting.
// Default: 1 second
WindowDuration time.Duration
}
RateLimitConfig configures the rate limiting middleware.
func DefaultRateLimitConfig ¶
func DefaultRateLimitConfig() RateLimitConfig
DefaultRateLimitConfig returns a default rate limit configuration.
type Response ¶
type Response[T any] struct { Data T `json:"data,omitempty"` Errors []Error `json:"errors,omitempty"` Message string `json:"message,omitempty"` }
Response is a generic wrapper for all API responses.
This provides a consistent structure across all endpoints:
- Data: The actual response payload (type-safe via generics)
- Errors: List of field-level errors (for validation failures)
- Message: Human-readable message
Example success response:
{
"data": {"id": 123, "name": "John"},
"message": "user created"
}
Example error response:
{
"errors": [{"field": "email", "message": "invalid format"}],
"message": "validation failed"
}
type SQLCredentialValidator ¶
type SQLCredentialValidator struct {
// contains filtered or unexported fields
}
SQLCredentialValidator validates against a database table. Expects a table with client_id and passkey columns.
func NewSQLCredentialValidator ¶
func NewSQLCredentialValidator(db *sql.DB, query string) *SQLCredentialValidator
NewSQLCredentialValidator creates a validator that queries a database.
The query should return the passkey for a given client_id. Example query: "SELECT passkey FROM service_credentials WHERE client_id = $1 AND is_active = true"
Example:
validator := httpserver.NewSQLCredentialValidator(
db,
"SELECT passkey FROM service_credentials WHERE client_id = $1",
)
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server wraps http.Server with graceful shutdown, signal handling, and lifecycle logging.
Create a Server using New():
server := httpserver.New(
httpserver.WithConfig(httpserver.ProductionConfig()),
httpserver.WithServiceName("my-api"),
httpserver.WithHandler(mux),
)
// Blocks until shutdown signal (SIGTERM, SIGINT) or context cancellation
if err := server.ListenAndServe(ctx); err != nil {
log.Fatal(err)
}
func New ¶
New creates a new Server with the provided options.
At minimum, you must provide a handler using WithHandler(). If no config is provided, DefaultConfig() is used.
Example:
server := httpserver.New(
httpserver.WithServiceName("my-api"),
httpserver.WithHandler(mux),
httpserver.WithMiddleware(
httpserver.Recovery(logger),
httpserver.RequestID(),
),
)
func (*Server) Addr ¶
Addr returns the server's listen address.
This is useful when using ":0" to let the OS pick a random port. Note: This returns the configured address, not the actual bound address. For the actual address after binding, check the listener.
func (*Server) ListenAndServe ¶
ListenAndServe starts the server and blocks until shutdown.
The server will shut down gracefully when:
- The provided context is cancelled
- SIGTERM or SIGINT is received
During shutdown:
- Server stops accepting new connections
- Waits up to ShutdownTimeout for in-flight requests
- Returns nil on clean shutdown, or error if shutdown times out
Example:
ctx := context.Background()
if err := server.ListenAndServe(ctx); err != nil {
log.Fatal(err)
}
func (*Server) ListenAndServeTLS ¶
ListenAndServeTLS starts the server with TLS and blocks until shutdown.
This is equivalent to ListenAndServe but uses the provided certificate and key files for TLS.
Example:
ctx := context.Background()
if err := server.ListenAndServeTLS(ctx, "cert.pem", "key.pem"); err != nil {
log.Fatal(err)
}
func (*Server) ServiceName ¶
ServiceName returns the configured service name.
func (*Server) Shutdown ¶
Shutdown initiates graceful shutdown of the server.
This is useful when you want to trigger shutdown programmatically instead of waiting for signals.
Example:
// In another goroutine or signal handler
if err := server.Shutdown(ctx); err != nil {
log.Printf("shutdown error: %v", err)
}
type ServiceAuthConfig ¶
type ServiceAuthConfig struct {
// Validator checks if the client_id and passkey are valid.
Validator CredentialValidator
// ClientIDHeader is the header name for client ID.
// Default: "Client-ID"
ClientIDHeader string
// PassKeyHeader is the header name for passkey.
// Default: "Pass-Key"
PassKeyHeader string
}
ServiceAuthConfig configures the service-to-service authentication middleware.
type TracingConfig ¶
type TracingConfig struct {
// TracerProvider is the OTel tracer provider.
// If nil, uses otel.GetTracerProvider().
TracerProvider trace.TracerProvider
// Propagator is the context propagator.
// If nil, uses otel.GetTextMapPropagator().
Propagator propagation.TextMapPropagator
// SkipPaths are paths that should not be traced.
SkipPaths []string
// SpanNameFormatter formats the span name.
// Default: "HTTP {method} {path}"
SpanNameFormatter func(r *http.Request) string
// contains filtered or unexported fields
}
TracingConfig configures the tracing middleware.
func DefaultTracingConfig ¶
func DefaultTracingConfig() TracingConfig
DefaultTracingConfig returns a default tracing configuration.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
adapters
|
|
|
echo
Package echo provides middleware adapters for Echo framework.
|
Package echo provides middleware adapters for Echo framework. |
|
fiber
Package fiber provides middleware adapters for Fiber framework.
|
Package fiber provides middleware adapters for Fiber framework. |
|
gin
Package gin provides middleware adapters for Gin framework.
|
Package gin provides middleware adapters for Gin framework. |
|
grpcgateway
Package grpcgateway provides middleware adapters for grpc-gateway.
|
Package grpcgateway provides middleware adapters for grpc-gateway. |