Documentation
¶
Overview ¶
Package chikit provides production-grade middleware components for Chi routers.
This file contains the core error types used throughout chikit for structured API error responses. These types enable consistent, Stripe-style error handling across all middleware and handlers.
Package chikit provides state management for context-based response handling.
Index ¶
- Variables
- func APIKey(validator APIKeyValidator, opts ...APIKeyOption) func(http.Handler) http.Handler
- func APIKeyFromContext(ctx context.Context) (string, bool)
- func ActiveHandlerCount() int
- func AddHeader(r *http.Request, key, value string)
- func BearerToken(validator BearerTokenValidator, opts ...BearerTokenOption) func(http.Handler) http.Handler
- func BearerTokenFromContext(ctx context.Context) (string, bool)
- func Binder(opts ...BindOption) func(http.Handler) http.Handler
- func ExtractHeader(header, ctxKey string, opts ...HeaderExtractorOption) func(http.Handler) http.Handler
- func Handler(opts ...HandlerOption) func(http.Handler) http.Handler
- func HasState(ctx context.Context) bool
- func HeaderFromContext(ctx context.Context, key string) (any, bool)
- func JSON(r *http.Request, dest any) bool
- func MaxBodySize(maxBytes int64, opts ...BodySizeOption) func(http.Handler) http.Handler
- func Query(r *http.Request, dest any) bool
- func RegisterValidation(tag string, fn validator.Func) error
- func SLO(tier SLOTier) func(http.Handler) http.Handler
- func SLOWithTarget(target time.Duration) func(http.Handler) http.Handler
- func SetError(r *http.Request, err *APIError)
- func SetHeader(r *http.Request, key, value string)
- func SetResponse(r *http.Request, status int, body any)
- func ValidateHeaders(opts ...ValidateHeadersOption) func(http.Handler) http.Handler
- func WaitForHandlers(ctx context.Context) error
- type APIError
- type APIKeyOption
- type APIKeyValidator
- type BearerTokenOption
- type BearerTokenValidator
- type BindOption
- type BodySizeOption
- type FieldError
- type HandlerOption
- func WithAbandonCallback(fn func(*http.Request)) HandlerOption
- func WithCanonlog() HandlerOption
- func WithCanonlogFields(fn func(*http.Request) map[string]any) HandlerOption
- func WithGracefulShutdown(d time.Duration) HandlerOption
- func WithSLOs() HandlerOption
- func WithTimeout(d time.Duration) HandlerOption
- type HeaderExtractor
- type HeaderExtractorOption
- type MessageFormatter
- type RateLimitHeaderMode
- type RateLimitOption
- func RateLimitWithEndpoint() RateLimitOption
- func RateLimitWithHeader(header string) RateLimitOption
- func RateLimitWithHeaderMode(mode RateLimitHeaderMode) RateLimitOption
- func RateLimitWithHeaderRequired(header string) RateLimitOption
- func RateLimitWithIP() RateLimitOption
- func RateLimitWithName(name string) RateLimitOption
- func RateLimitWithQueryParam(param string) RateLimitOption
- func RateLimitWithQueryParamRequired(param string) RateLimitOption
- func RateLimitWithRealIP() RateLimitOption
- func RateLimitWithRealIPRequired() RateLimitOption
- type RateLimiter
- type SLOTier
- type State
- type ValidateHeaderConfig
- type ValidateHeaderOption
- type ValidateHeadersOption
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( ErrBadRequest = &APIError{Type: "request_error", Code: "bad_request", Message: "Bad request", Status: http.StatusBadRequest} ErrPaymentRequired = &APIError{Type: "request_error", Code: "payment_required", Message: "Payment required", Status: http.StatusPaymentRequired} ErrForbidden = &APIError{Type: "auth_error", Code: "forbidden", Message: "Forbidden", Status: http.StatusForbidden} ErrNotFound = &APIError{Type: "not_found", Code: "resource_not_found", Message: "Resource not found", Status: http.StatusNotFound} ErrMethodNotAllowed = &APIError{Type: "request_error", Code: "method_not_allowed", Message: "Method not allowed", Status: http.StatusMethodNotAllowed} ErrConflict = &APIError{Type: "request_error", Code: "conflict", Message: "Conflict", Status: http.StatusConflict} ErrGone = &APIError{Type: "request_error", Code: "gone", Message: "Resource gone", Status: http.StatusGone} ErrPayloadTooLarge = &APIError{Type: "request_error", Code: "payload_too_large", Message: "Payload too large", Status: http.StatusRequestEntityTooLarge} ErrUnprocessableEntity = &APIError{Type: "validation_error", Code: "unprocessable", Message: "Unprocessable entity", Status: http.StatusUnprocessableEntity} ErrRateLimited = &APIError{Type: "rate_limit_error", Code: "limit_exceeded", Message: "Rate limit exceeded", Status: http.StatusTooManyRequests} ErrInternal = &APIError{Type: "internal_error", Code: "internal", Message: "Internal server error", Status: http.StatusInternalServerError} ErrNotImplemented = &APIError{Type: "request_error", Code: "not_implemented", Message: "Not implemented", Status: http.StatusNotImplemented} ErrGatewayTimeout = &APIError{Type: "timeout_error", Code: "gateway_timeout", Message: "Request timed out", Status: http.StatusGatewayTimeout} )
Predefined sentinel errors
Functions ¶
func APIKey ¶
func APIKey(validator APIKeyValidator, opts ...APIKeyOption) func(http.Handler) http.Handler
APIKey returns middleware that validates API keys from a header. Returns 401 (Unauthorized) if the key is missing (when required) or invalid. The validated API key is stored in the request context and can be retrieved using APIKeyFromContext.
Example:
validator := func(key string) bool {
return key == "secret-key" // In production, check against DB/cache
}
r.Use(chikit.APIKey(validator))
With custom header:
r.Use(chikit.APIKey(validator, chikit.WithAPIKeyHeader("X-Custom-Key")))
Optional authentication:
r.Use(chikit.APIKey(validator, chikit.WithOptionalAPIKey()))
Example ¶
validator := func(key string) bool {
return key == "valid-api-key"
}
r := chi.NewRouter()
r.Use(chikit.APIKey(validator))
func APIKeyFromContext ¶
APIKeyFromContext retrieves the validated API key from the request context. Returns the key and true if present, or empty string and false if not present.
Example:
func handler(w http.ResponseWriter, r *http.Request) {
if key, ok := chikit.APIKeyFromContext(r.Context()); ok {
log.Printf("Request authenticated with key: %s", key)
}
}
func ActiveHandlerCount ¶
func ActiveHandlerCount() int
ActiveHandlerCount returns the number of handler goroutines currently running. This is useful for monitoring during graceful shutdown or for metrics. Only counts handlers started with WithTimeout enabled.
func AddHeader ¶
AddHeader adds a response header value in the request context. If wrapper middleware is not present (state is nil), this is a no-op. If state is frozen (response already written), this is a no-op. Use HasState() to check if wrapper middleware is active.
func BearerToken ¶
func BearerToken(validator BearerTokenValidator, opts ...BearerTokenOption) func(http.Handler) http.Handler
BearerToken returns middleware that validates bearer tokens from the Authorization header. Expects the header format "Bearer <token>". Returns 401 (Unauthorized) if the token is missing (when required), malformed, or invalid. The validated token is stored in the request context and can be retrieved using BearerTokenFromContext.
Example:
validator := func(token string) bool {
return jwt.Validate(token) // Use your JWT library
}
r.Use(chikit.BearerToken(validator))
Optional authentication:
r.Use(chikit.BearerToken(validator, chikit.WithOptionalBearerToken()))
func BearerTokenFromContext ¶
BearerTokenFromContext retrieves the validated bearer token from the request context. Returns the token and true if present, or empty string and false if not present.
Example:
func handler(w http.ResponseWriter, r *http.Request) {
if token, ok := chikit.BearerTokenFromContext(r.Context()); ok {
claims := jwt.Parse(token)
log.Printf("User: %s", claims.Subject)
}
}
func Binder ¶
func Binder(opts ...BindOption) func(http.Handler) http.Handler
Binder returns middleware with optional configuration.
func ExtractHeader ¶
func ExtractHeader(header, ctxKey string, opts ...HeaderExtractorOption) func(http.Handler) http.Handler
ExtractHeader creates middleware that extracts a header and stores it in context. The header value (or transformed value from validator) is stored in the request context under the specified ctxKey and can be retrieved using HeaderFromContext.
Parameters:
- header: The HTTP header name to extract (e.g., "X-API-Key")
- ctxKey: The context key to store the value under (e.g., "api_key")
- opts: Optional configuration (ExtractRequired, ExtractDefault, ExtractWithValidator)
Returns 400 (Bad Request) if:
- A required header is missing and no default is provided
- Validation fails (validator returns an error)
Example:
// Simple extraction
r.Use(chikit.ExtractHeader("X-Request-ID", "request_id"))
// Required with validation
r.Use(chikit.ExtractHeader("X-Tenant-ID", "tenant_id",
chikit.ExtractRequired(),
chikit.ExtractWithValidator(validateUUID)))
// Optional with default
r.Use(chikit.ExtractHeader("X-Client-Version", "version",
chikit.ExtractDefault("1.0.0")))
Example ¶
r := chi.NewRouter()
// Extract optional header with default
r.Use(chikit.ExtractHeader("X-Request-ID", "request_id",
chikit.ExtractDefault("unknown"),
))
r.Get("/", func(_ http.ResponseWriter, r *http.Request) {
if val, ok := chikit.HeaderFromContext(r.Context(), "request_id"); ok {
fmt.Printf("Request ID: %s\n", val)
}
})
func Handler ¶
func Handler(opts ...HandlerOption) func(http.Handler) http.Handler
Handler returns middleware that manages response state and writes responses.
Example ¶
r := chi.NewRouter()
r.Use(chikit.Handler())
r.Get("/", func(_ http.ResponseWriter, r *http.Request) {
chikit.SetResponse(r, http.StatusOK, map[string]string{"status": "ok"})
})
Example (Timeout) ¶
r := chi.NewRouter()
r.Use(chikit.Handler(
chikit.WithTimeout(30*time.Second),
chikit.WithCanonlog(),
))
r.Get("/", func(_ http.ResponseWriter, r *http.Request) {
// Handler code runs with a 30-second deadline.
// If the handler doesn't complete in time, a 504 Gateway Timeout
// is returned to the client immediately.
chikit.SetResponse(r, http.StatusOK, map[string]string{"status": "ok"})
})
Example (TimeoutWithGrace) ¶
r := chi.NewRouter()
r.Use(chikit.Handler(
chikit.WithTimeout(30*time.Second),
chikit.WithGracefulShutdown(10*time.Second),
chikit.WithAbandonCallback(func(r *http.Request) {
// Handler didn't exit within grace period after timeout.
// Log this for investigation - may indicate a stuck handler.
fmt.Printf("handler abandoned: %s %s\n", r.Method, r.URL.Path)
}),
))
func HeaderFromContext ¶
HeaderFromContext retrieves a value from the request context by key. Returns the value and true if present, or nil and false if not present. The returned value has type 'any' and should be type-asserted to the expected type.
Example:
func handler(w http.ResponseWriter, r *http.Request) {
if val, ok := chikit.HeaderFromContext(r.Context(), "api_key"); ok {
apiKey := val.(string)
log.Printf("API Key: %s", apiKey)
}
// With type assertion for transformed values
if val, ok := chikit.HeaderFromContext(r.Context(), "tenant_id"); ok {
tenantID := val.(uuid.UUID)
// Use typed value
}
}
func JSON ¶
JSON decodes request body into dest and validates it. Returns true if binding and validation succeeded, false otherwise. When validation fails, an error is set in the wrapper context (if available).
Body size limits: If validate.MaxBodySize middleware is active, requests exceeding the limit during decode return ErrPayloadTooLarge (413). This handles chunked transfers and requests with missing/incorrect Content-Length headers.
Example ¶
package main
import (
"net/http"
"github.com/nhalm/chikit"
)
func main() {
type Request struct {
Email string `json:"email" validate:"required,email"`
}
handler := func(_ http.ResponseWriter, r *http.Request) {
var req Request
if !chikit.JSON(r, &req) {
return // Validation error already set
}
chikit.SetResponse(r, http.StatusOK, req)
}
_ = handler
}
Output:
func MaxBodySize ¶
MaxBodySize returns middleware that limits request body size.
The middleware provides two-stage protection:
- Content-Length check: Requests with Content-Length exceeding the limit are rejected with 413 immediately, before the handler runs
- MaxBytesReader wrapper: All request bodies are wrapped with http.MaxBytesReader as defense-in-depth, catching chunked transfers and missing/incorrect Content-Length
When used with bind.JSON, the second stage is automatic:
r.Use(chikit.MaxBodySize(1024 * 1024))
r.Post("/users", func(w http.ResponseWriter, r *http.Request) {
var req CreateUserRequest
if !bind.JSON(r, &req) {
return // Returns 413 if body exceeds limit during decode
}
})
Returns 413 (Request Entity Too Large) when the limit is exceeded.
Basic usage:
r.Use(chikit.MaxBodySize(10 * 1024 * 1024)) // 10MB limit
Example ¶
r := chi.NewRouter() r.Use(chikit.Handler()) r.Use(chikit.MaxBodySize(1024 * 1024)) // 1MB limit
func Query ¶
Query decodes query parameters into dest and validates it. Returns true if binding and validation succeeded, false otherwise. When validation fails, an error is set in the wrapper context (if available).
func RegisterValidation ¶
RegisterValidation registers a custom validation function. Must be called at startup before handling requests.
func SLO ¶
SLO sets a predefined SLO tier in context. The tier determines the latency target:
- SLOCritical: 50ms
- SLOHighFast: 100ms
- SLOHighSlow: 1000ms
- SLOLow: 5000ms
func SLOWithTarget ¶
SLOWithTarget sets a custom SLO target in context. The tier is logged as "custom".
func SetError ¶
SetError sets an error response in the request context. If wrapper middleware is not present (state is nil), this is a no-op. If state is frozen (response already written), this is a no-op. Use HasState() to check if wrapper middleware is active.
Example ¶
package main
import (
"net/http"
"github.com/nhalm/chikit"
)
func main() {
handler := func(_ http.ResponseWriter, r *http.Request) {
// Return a 404 with custom message
chikit.SetError(r, chikit.ErrNotFound.With("User not found"))
}
_ = handler
}
Output:
func SetHeader ¶
SetHeader sets a response header in the request context. If wrapper middleware is not present (state is nil), this is a no-op. If state is frozen (response already written), this is a no-op. Use HasState() to check if wrapper middleware is active.
func SetResponse ¶
SetResponse sets a success response in the request context. If wrapper middleware is not present (state is nil), this is a no-op. If state is frozen (response already written), this is a no-op. Use HasState() to check if wrapper middleware is active.
func ValidateHeaders ¶
func ValidateHeaders(opts ...ValidateHeadersOption) func(http.Handler) http.Handler
ValidateHeaders returns middleware that validates request headers according to the given rules. For each rule, checks if the header is present (when required), validates against allow/deny lists, and enforces case sensitivity settings. Returns 400 (Bad Request) for all validation failures.
Example:
r.Use(chikit.ValidateHeaders(
chikit.ValidateWithHeader("Content-Type",
chikit.ValidateRequired(),
chikit.ValidateAllowList("application/json", "application/xml")),
chikit.ValidateWithHeader("X-Custom-Header",
chikit.ValidateDenyList("forbidden-value")),
))
Example ¶
r := chi.NewRouter()
r.Use(chikit.ValidateHeaders(
chikit.ValidateWithHeader("Content-Type",
chikit.ValidateRequired(),
chikit.ValidateAllowList("application/json"),
),
))
func WaitForHandlers ¶
WaitForHandlers waits for all spawned handler goroutines to complete. Call this during graceful shutdown after http.Server.Shutdown(). Returns nil if all handlers complete, or ctx.Err() if the context deadline is exceeded.
Example graceful shutdown pattern:
srv := &http.Server{Addr: ":8080", Handler: r}
go srv.ListenAndServe()
<-shutdownSignal
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
srv.Shutdown(ctx) // Wait for in-flight requests
chikit.WaitForHandlers(ctx) // Wait for handler goroutines
Note: If the context deadline is exceeded, WaitForHandlers returns immediately. Use ActiveHandlerCount to monitor how many handlers are still running.
Types ¶
type APIError ¶
type APIError struct {
Type string `json:"type"`
Code string `json:"code,omitempty"`
Message string `json:"message"`
Param string `json:"param,omitempty"`
Errors []FieldError `json:"errors,omitempty"`
Status int `json:"-"`
}
APIError represents a structured API error response.
func NewValidationError ¶
func NewValidationError(errors []FieldError) *APIError
NewValidationError creates a validation error with multiple field errors.
type APIKeyOption ¶
type APIKeyOption func(*apiKeyConfig)
APIKeyOption configures APIKey middleware.
func WithAPIKeyHeader ¶
func WithAPIKeyHeader(header string) APIKeyOption
WithAPIKeyHeader sets the header to read the API key from. Default is "X-API-Key".
func WithOptionalAPIKey ¶
func WithOptionalAPIKey() APIKeyOption
WithOptionalAPIKey makes the API key optional. When set, requests without an API key are allowed through without validation. The API key will not be present in the context for these requests.
type APIKeyValidator ¶
APIKeyValidator validates an API key and returns true if valid. The validator function is provided by the application and can check against a database, cache, or any other validation mechanism.
Thread safety: Validators are called concurrently from multiple goroutines and must be safe for concurrent use. Avoid shared mutable state.
type BearerTokenOption ¶
type BearerTokenOption func(*bearerTokenConfig)
BearerTokenOption configures BearerToken middleware.
func WithOptionalBearerToken ¶
func WithOptionalBearerToken() BearerTokenOption
WithOptionalBearerToken makes the bearer token optional. When set, requests without a bearer token are allowed through without validation. The token will not be present in the context for these requests.
type BearerTokenValidator ¶
BearerTokenValidator validates a bearer token and returns true if valid. The validator function is provided by the application and can perform JWT validation, token lookup, or any other validation mechanism.
Thread safety: Validators are called concurrently from multiple goroutines and must be safe for concurrent use. Avoid shared mutable state.
type BindOption ¶
type BindOption func(*bindConfig)
BindOption configures the bind middleware.
func BindWithFormatter ¶
func BindWithFormatter(fn MessageFormatter) BindOption
BindWithFormatter sets a custom message formatter for validation errors.
type BodySizeOption ¶
type BodySizeOption func(*validateBodySizeConfig)
BodySizeOption configures MaxBodySize middleware.
type FieldError ¶
type FieldError struct {
Param string `json:"param"`
Code string `json:"code"`
Message string `json:"message"`
}
FieldError represents a validation error for a specific field.
type HandlerOption ¶
type HandlerOption func(*config)
HandlerOption configures the Handler middleware.
func WithAbandonCallback ¶
func WithAbandonCallback(fn func(*http.Request)) HandlerOption
WithAbandonCallback sets a function to call when a handler doesn't exit within the grace timeout. Use this for metrics or alerting.
func WithCanonlog ¶
func WithCanonlog() HandlerOption
WithCanonlog enables canonical logging for requests. Creates a logger at request start and flushes it after response. Logs method, path, route, status, and duration_ms for each request. Errors set via SetError are automatically logged.
func WithCanonlogFields ¶
func WithCanonlogFields(fn func(*http.Request) map[string]any) HandlerOption
WithCanonlogFields adds custom fields to each log entry. The function receives the request and returns fields to add. Called at request start, before the handler executes.
func WithGracefulShutdown ¶
func WithGracefulShutdown(d time.Duration) HandlerOption
WithGracefulShutdown sets how long to wait for a handler goroutine to exit after timeout fires. This grace period allows handlers to complete cleanup (e.g., database rollbacks) after the 504 response is sent to the client.
After the grace period, the handler is considered abandoned. If canonlog is enabled, an error is logged. Use WithAbandonCallback for metrics/alerting.
Default is 5 seconds. Can be specified before or after WithTimeout.
func WithSLOs ¶
func WithSLOs() HandlerOption
WithSLOs enables SLO status logging. Requires WithCanonlog() to be enabled. Reads SLO tier and target from context (set via SLO or SLOWithTarget) and logs slo_class and slo_status (PASS or FAIL) based on request duration.
func WithTimeout ¶
func WithTimeout(d time.Duration) HandlerOption
WithTimeout sets a maximum duration for handler execution. If the handler doesn't complete within the timeout, a 504 Gateway Timeout response is returned immediately. The context is cancelled so DB/HTTP calls can exit early. The handler goroutine continues running but its response is discarded.
When timeout is enabled, handlers run in a separate goroutine. You MUST call WaitForHandlers during graceful shutdown to wait for handler goroutines to complete before process exit. See WaitForHandlers for the shutdown pattern.
Default graceful shutdown timeout is 5 seconds. Use WithGracefulShutdown to change this value. Options can be specified in any order.
Note: Go cannot forcibly terminate goroutines. If handlers ignore context cancellation (CGO calls, tight CPU loops), they continue running after the 504 response. Use WithAbandonCallback to track this with metrics.
type HeaderExtractor ¶
type HeaderExtractor struct {
// contains filtered or unexported fields
}
HeaderExtractor extracts a header value and stores it in the request context. Supports optional headers, default values, and custom validation/transformation.
type HeaderExtractorOption ¶
type HeaderExtractorOption func(*HeaderExtractor)
HeaderExtractorOption configures a HeaderExtractor middleware.
func ExtractDefault ¶
func ExtractDefault(val string) HeaderExtractorOption
ExtractDefault provides a default value if the header is missing. The default takes precedence over the Required setting - if a default is provided, the header becomes effectively optional.
func ExtractRequired ¶
func ExtractRequired() HeaderExtractorOption
ExtractRequired marks the header as required. Returns 400 (Bad Request) if the header is missing and no default is provided.
func ExtractWithValidator ¶
func ExtractWithValidator(fn func(string) (any, error)) HeaderExtractorOption
ExtractWithValidator provides a custom validator that can transform the header value. The validator receives the header value (string) and returns:
- The transformed value (any type) to store in context
- An error if validation fails (returns 400 to client)
Use this to parse and validate headers like UUIDs, timestamps, enums, etc.
Example:
validator := func(val string) (any, error) {
if val == "admin" || val == "user" {
return val, nil
}
return nil, fmt.Errorf("must be 'admin' or 'user'")
}
type MessageFormatter ¶
MessageFormatter generates human-readable message from validation error. Parameters: field name, validation tag, tag parameter (e.g., "10" from "min=10")
type RateLimitHeaderMode ¶
type RateLimitHeaderMode int
RateLimitHeaderMode controls when rate limit headers are included in responses.
const ( // RateLimitHeadersAlways includes rate limit headers on all responses (default). // Headers: RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset // On 429: Also includes Retry-After RateLimitHeadersAlways RateLimitHeaderMode = iota // RateLimitHeadersOnLimitExceeded includes rate limit headers only on 429 responses. // Headers on 429: RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset, Retry-After RateLimitHeadersOnLimitExceeded // RateLimitHeadersNever never includes rate limit headers in any response. // Use this when you want rate limiting without exposing limits to clients. RateLimitHeadersNever )
type RateLimitOption ¶
type RateLimitOption func(*RateLimiter)
RateLimitOption configures a RateLimiter.
func RateLimitWithEndpoint ¶
func RateLimitWithEndpoint() RateLimitOption
RateLimitWithEndpoint adds the HTTP method and path to the rate limiting key. Key component format: "<method>:<path>". Method and path are always present.
func RateLimitWithHeader ¶
func RateLimitWithHeader(header string) RateLimitOption
RateLimitWithHeader adds a header value to the rate limiting key. If the header is missing, rate limiting is skipped for that request.
func RateLimitWithHeaderMode ¶
func RateLimitWithHeaderMode(mode RateLimitHeaderMode) RateLimitOption
RateLimitWithHeaderMode configures when rate limit headers are included in responses.
func RateLimitWithHeaderRequired ¶
func RateLimitWithHeaderRequired(header string) RateLimitOption
RateLimitWithHeaderRequired adds a header value to the rate limiting key. Returns 400 Bad Request when the header is missing.
func RateLimitWithIP ¶
func RateLimitWithIP() RateLimitOption
RateLimitWithIP adds the client IP address (from RemoteAddr) to the rate limiting key. Use this for direct connections without a proxy. RemoteAddr is always present.
func RateLimitWithName ¶
func RateLimitWithName(name string) RateLimitOption
RateLimitWithName sets a prefix for rate limit keys. Use to prevent key collisions when layering multiple rate limiters.
func RateLimitWithQueryParam ¶
func RateLimitWithQueryParam(param string) RateLimitOption
RateLimitWithQueryParam adds a query parameter value to the rate limiting key. If the parameter is missing, rate limiting is skipped for that request.
func RateLimitWithQueryParamRequired ¶
func RateLimitWithQueryParamRequired(param string) RateLimitOption
RateLimitWithQueryParamRequired adds a query parameter value to the rate limiting key. Returns 400 Bad Request when the parameter is missing.
func RateLimitWithRealIP ¶
func RateLimitWithRealIP() RateLimitOption
RateLimitWithRealIP adds the client IP from X-Forwarded-For or X-Real-IP headers. Use this when behind a proxy/load balancer. If neither header is present, rate limiting is skipped for that request.
SECURITY: Only use this behind a trusted reverse proxy that sets these headers. Without a proxy, clients can spoof X-Forwarded-For to bypass rate limits.
func RateLimitWithRealIPRequired ¶
func RateLimitWithRealIPRequired() RateLimitOption
RateLimitWithRealIPRequired adds the client IP from X-Forwarded-For or X-Real-IP headers. Use this when behind a proxy/load balancer. Returns 400 Bad Request when neither header is present.
SECURITY: Only use this behind a trusted reverse proxy that sets these headers. Without a proxy, clients can spoof X-Forwarded-For to bypass rate limits.
type RateLimiter ¶
type RateLimiter struct {
// contains filtered or unexported fields
}
RateLimiter implements rate limiting middleware.
func NewRateLimiter ¶
func NewRateLimiter(st store.Store, limit int, window time.Duration, opts ...RateLimitOption) *RateLimiter
NewRateLimiter creates a new rate limiter with the given store, limit, and window. Use RateLimitWith* options to configure key dimensions and behavior. Returns 429 (Too Many Requests) when the limit is exceeded, with standard rate limit headers and a Retry-After header indicating seconds until reset. Returns 400 (Bad Request) if a *Required dimension is missing. Returns 500 (Internal Server Error) if the store operation fails.
At least one key dimension option must be provided. Panics if no key dimensions are configured.
Key dimension options:
- RateLimitWithIP: Add RemoteAddr IP to key (direct connections)
- RateLimitWithRealIP / RateLimitWithRealIPRequired: Add X-Forwarded-For/X-Real-IP to key
- RateLimitWithEndpoint: Add method:path to key
- RateLimitWithHeader / RateLimitWithHeaderRequired: Add header value to key
- RateLimitWithQueryParam / RateLimitWithQueryParamRequired: Add query parameter to key
Other options:
- RateLimitWithName: Set key prefix for collision prevention
- RateLimitWithHeaderMode: Configure header visibility (default: RateLimitHeadersAlways)
Example ¶
st := store.NewMemory() defer st.Close() // Rate limit by IP: 100 requests per minute limiter := chikit.NewRateLimiter(st, 100, time.Minute, chikit.RateLimitWithIP(), ) r := chi.NewRouter() r.Use(limiter.Handler)
Example (MultiDimensional) ¶
st := store.NewMemory()
defer st.Close()
// Rate limit by tenant + endpoint: 100 requests per minute
limiter := chikit.NewRateLimiter(st, 100, time.Minute,
chikit.RateLimitWithHeaderRequired("X-Tenant-ID"),
chikit.RateLimitWithEndpoint(),
)
r := chi.NewRouter()
r.Use(limiter.Handler)
func (*RateLimiter) Handler ¶
func (l *RateLimiter) Handler(next http.Handler) http.Handler
Handler returns the rate limiting middleware. Sets the following headers based on header mode:
- RateLimit-Limit: The rate limit ceiling for the current window
- RateLimit-Remaining: Number of requests remaining in the current window
- RateLimit-Reset: Unix timestamp when the current window resets
- Retry-After: (only when limited) Seconds until the window resets
These headers follow the IETF draft-ietf-httpapi-ratelimit-headers specification.
type SLOTier ¶
type SLOTier string
SLOTier represents an SLO classification level.
const ( // SLOCritical is for essential functions requiring 99.99% availability. SLOCritical SLOTier = "critical" // SLOHighFast is for user-facing requests requiring quick responses (99.9% availability, 100ms latency). SLOHighFast SLOTier = "high_fast" // SLOHighSlow is for important requests that can tolerate higher latency (99.9% availability, 1000ms latency). SLOHighSlow SLOTier = "high_slow" // SLOLow is for background tasks or non-interactive functions (99% availability). SLOLow SLOTier = "low" )
type State ¶
type State struct {
// contains filtered or unexported fields
}
State holds the response state for a request.
type ValidateHeaderConfig ¶
type ValidateHeaderConfig struct {
Name string
Required bool
AllowedList []string
DeniedList []string
CaseSensitive bool
}
ValidateHeaderConfig defines validation rules for a header.
type ValidateHeaderOption ¶
type ValidateHeaderOption func(*ValidateHeaderConfig)
ValidateHeaderOption configures a header validation rule.
func ValidateAllowList ¶
func ValidateAllowList(values ...string) ValidateHeaderOption
ValidateAllowList sets the list of allowed values for a header. If set, only values in this list are permitted. Returns 400 if the value is not in the list.
func ValidateCaseSensitive ¶
func ValidateCaseSensitive() ValidateHeaderOption
ValidateCaseSensitive makes header value comparisons case-sensitive. By default, comparisons are case-insensitive.
func ValidateDenyList ¶
func ValidateDenyList(values ...string) ValidateHeaderOption
ValidateDenyList sets the list of denied values for a header. If set, values in this list are explicitly forbidden. Returns 400 if the value is in the list.
func ValidateRequired ¶
func ValidateRequired() ValidateHeaderOption
ValidateRequired marks a header as required.
type ValidateHeadersOption ¶
type ValidateHeadersOption func(*validateHeadersConfig)
ValidateHeadersOption configures ValidateHeaders middleware.
func ValidateWithHeader ¶
func ValidateWithHeader(name string, opts ...ValidateHeaderOption) ValidateHeadersOption
ValidateWithHeader adds a header validation rule with the given name and options.