router

package
v1.27.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 11, 2026 License: Apache-2.0 Imports: 41 Imported by: 0

Documentation

Overview

Package router provides an HTTP router for the Nucleus framework, built on top of Go's standard net/http.ServeMux (Go 1.22+). It includes a default middleware stack, response helpers, CSRF protection, and request binding with validation.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrNilContextWriter        = errors.New("router.Context: response writer is nil")
	ErrNilContextRequest       = errors.New("router.Context: request is nil")
	ErrTemplateEngineNotSet    = errors.New("router.Context: template engine is not configured")
	ErrTemplateNameRequired    = errors.New("router.Context: template name is required")
	ErrFilePathRequired        = errors.New("router.Context: file path is required")
	ErrSessionManagerNotSet    = errors.New("router.Context: session manager is not configured")
	ErrDownloadFilenameInvalid = errors.New("router.Context: download filename is invalid")
)
View Source
var ErrCSRFEncryptionKey = errors.New("router: CSRFOptions.EncryptionKey must be exactly 32 bytes (AES-256) when EnableXSRFCookie is set")

ErrCSRFEncryptionKey is returned by NewCSRFMiddleware when EnableXSRFCookie is true but EncryptionKey is not exactly csrfEncryptionKeySize bytes. See ADR-006.

Functions

func Bind

func Bind(r *http.Request, v interface{}) error

Bind decodes the request body as JSON into v, then validates it using struct validate tags. Returns a *DomainError if decoding or validation fails. Bodies are capped at 1 MiB (413 beyond it); use BindMax to raise the cap for endpoints that legitimately accept larger payloads.

WARNING — unlike BindForm, Bind applies no mass-assignment guard: a client can set any json-exposed field, including server-owned ones such as model.BaseModel's id/created_at/updated_at. encoding/json offers no per-field skip-on-decode without also hiding the field from responses, so the guard cannot be applied transparently here. Bind JSON onto a dedicated input type that omits server-owned fields, or zero those fields after decoding, when the target embeds a persistence model.

func BindForm

func BindForm(r *http.Request, v interface{}) error

BindForm decodes an application/x-www-form-urlencoded or multipart/form-data request into v, then validates it using struct validate tags — the form counterpart of Bind.

Field resolution order: a `form:"name"` tag wins, then `json:"name"`, then the case-insensitive field name (first match wins if two form keys differ only by case); `form:"-"` skips a field. Supported field kinds: string, bool, signed and unsigned integers, floats, time.Time (RFC 3339, the datetime-local format 2006-01-02T15:04 — parsed as UTC since the wire format carries no offset — or 2006-01-02) and pointers to those. Embedded value structs are flattened; pointer embeddings are not traversed. Form keys without a matching field are ignored; present-but-empty values leave the field at its zero value so optional numeric inputs submit cleanly. Checkbox values "on" bind as true. Bodies are capped at 10 MiB.

Mass-assignment protection: fields tagged `db:"pk"` or `db:"readonly"` are server-owned (model.BaseModel's ID/CreatedAt/UpdatedAt) and are never bound from request input — a client-submitted `id` or `created_at` is ignored, leaving whatever the caller pre-set (zero on a create, the loaded value on an update). This is skip, not clear: binding onto a loaded record preserves its identity. NOTE the asymmetry — Bind (JSON) does NOT yet apply this guard (encoding/json offers no per-field skip without also hiding the field from responses); callers binding JSON onto persistence models must still zero server-owned fields themselves or bind through an input type.

Returns a *DomainError if parsing, conversion, or validation fails.

func BindMax

func BindMax(r *http.Request, v interface{}, maxBytes int64) error

BindMax is Bind with a caller-chosen body cap in bytes. maxBytes <= 0 falls back to the default 1 MiB cap — an accidental zero must never mean "unlimited".

func CORSMiddleware

func CORSMiddleware(opts CORSOptions) func(http.Handler) http.Handler

CORSMiddleware returns middleware that handles Cross-Origin Resource Sharing. It processes preflight OPTIONS requests and sets the appropriate CORS headers on all responses.

func CSRFMiddleware

func CSRFMiddleware(opts CSRFOptions) func(http.Handler) http.Handler

CSRFMiddleware returns middleware that protects against cross-site request forgery. It implements a two-layer approach (Laravel-style): 1. Origin verification via Sec-Fetch-Site header (if enabled) 2. Traditional CSRF token validation as fallback

Features: - Origin verification for modern browsers - Session-based or cookie-based token storage - Encrypted X-XSRF-TOKEN cookie for JavaScript frameworks - Token rotation for enhanced security - Configurable origin-only mode and same-site allowance

CSRFMiddleware panics on a misconfiguration (the regexp.MustCompile pattern) — a bad CSRF config is a deployment error that should crash the process at startup, not serve requests with a weak key. The panic fires once, at middleware-chain construction, never on the request path. Use NewCSRFMiddleware for a non-panicking, error-returning alternative. See ADR-006.

func CSRFToken

func CSRFToken(r *http.Request) string

CSRFToken returns the CSRF token for the current request, for templates to embed in forms. When the CSRF middleware is in the chain it returns the exact token that middleware resolved (injected into the context) — authoritative across cookie/session storage and any configured session key. Absent the middleware it falls back to the default cookie/session lookup.

func Compress

func Compress(level int) func(http.Handler) http.Handler

Compress returns middleware that gzip-compresses response bodies for clients that accept gzip encoding. level follows compress/flate constants.

func ContextHandler

func ContextHandler(handlers ...Handler) http.HandlerFunc

ContextHandler adapts a Handler to http.HandlerFunc.

func Created

func Created(w http.ResponseWriter, data interface{})

Created writes a 201 Created response with the given JSON data.

func DefaultStack

func DefaultStack(logger *slog.Logger, opts *routerOpts) []func(http.Handler) http.Handler

DefaultStack returns the standard middleware chain for Nucleus applications.

func Error

func Error(w http.ResponseWriter, r *http.Request, err error, logger ...*slog.Logger)

Error writes an error as a structured JSON response. If the error is a *DomainError, its status code and details are used; otherwise a generic 500 is returned.

func GetReqID

func GetReqID(ctx context.Context) string

GetReqID returns the request ID from context, or empty string.

func IsWebSocketUpgrade

func IsWebSocketUpgrade(r *http.Request) bool

func JSON

func JSON(w http.ResponseWriter, status int, data interface{})

JSON writes a JSON response with the given status code and data.

func Matched

func Matched(r *http.Request) bool

Matched reports whether a registered route serves the request, as the caller holds it. The dispatching Mux resolves the route against the request's method and path each time Matched is called, so a middleware mounted with Use can let an unregistered path fall through to the mux's own 404 instead of answering for a handler that does not exist — the framework's default-deny authorizer and the CSRF middleware both do, through WhenMatched. The decision follows the request as each middleware sees it: a middleware that rewrites the path changes the answer for everything after it.

The decision sees through mounted sub-routers (Route, or Mount with a *Mux): at the parent, a path under a mount prefix counts as matched only when the sub-router — or a sub-router of its own, any depth down — has a route for the rest of the path, so a gate mounted at the root judges the same route table a gate inside the mount would. A handler mounted with Mount that is not a Mux is opaque, and everything under its prefix reports true. A method-only mismatch (the path is registered for other methods) reports false, and the mux answers 405.

When no Mux has dispatched the request — the middleware is wrapped around a plain http.Handler, or the test calls it directly — there is no routing decision to consult and Matched reports true, so every security layer keeps enforcing as if the route existed.

func NewCSRFMiddleware

func NewCSRFMiddleware(opts CSRFOptions) (func(http.Handler) http.Handler, error)

NewCSRFMiddleware builds the CSRF protection middleware, returning an error on a misconfiguration instead of panicking. Use this constructor when the caller wants to surface configuration errors through its own validation path; use CSRFMiddleware for the panic-on-misconfiguration (regexp.MustCompile-style) variant.

The error case today is EnableXSRFCookie set without a 32-byte EncryptionKey — see ADR-006.

func NoContent

func NoContent(w http.ResponseWriter)

NoContent writes a 204 No Content response.

func Paginate

func Paginate(r *http.Request, defaultSize int) (page, pageSize int)

Paginate extracts page and page_size from query parameters with defaults and bounds. page defaults to 1, page_size to defaultSize (max 100).

func RateLimitFromPolicy

func RateLimitFromPolicy(policy RateLimitPolicy) func(http.Handler) http.Handler

RateLimitFromPolicy builds the request limiter DefaultStack would mount for policy, with the default key (tenant and user from the context, else client IP) and the default route and role dimensions. It exists so an application can mount the limiter AFTER its identity middleware: the key reads the user id, the claims and the tenant from the request context, and a limiter mounted before the bearer is decoded only ever sees an IP. A policy with Requests <= 0 yields a pass-through.

func RateLimitMiddleware

func RateLimitMiddleware(opts RateLimitOptions) func(http.Handler) http.Handler

RateLimitMiddleware enforces a token-bucket request limit: capacity is Requests+Burst tokens, refilled continuously at Requests per Window. This is smoother than a fixed window (no boundary bursts of 2× the limit) — the historical "fixed-window" description here was wrong; the implementation has always been the token bucket below.

func RealIP

func RealIP(next http.Handler) http.Handler

RealIP rewrites r.RemoteAddr with the client IP taken from X-Forwarded-For / X-Real-IP — but ONLY when the immediate peer is a trusted proxy. On its own (the exported middleware) no proxies are trusted, so forwarding headers are ignored and r.RemoteAddr is left untouched. Use the router's WithTrustedProxies option (wired from the `trusted_proxies` config key) to honor forwarding headers behind a known load balancer. Trusting these headers unconditionally lets any client spoof its IP — evading per-IP rate limits and poisoning audit logs (H-N3).

func Recoverer

func Recoverer(next http.Handler) http.Handler

Recoverer catches panics in downstream handlers, logs the stack trace, and returns a 500 Internal Server Error response. It logs through the process default logger; DefaultStack uses RecovererWithLogger so the panic goes through the application's handler (redaction, attributes, sink) like every other line.

func RecovererWithLogger

func RecovererWithLogger(logger *slog.Logger) func(http.Handler) http.Handler

RecovererWithLogger is Recoverer logging through logger; nil falls back to slog.Default().

func RequestID

func RequestID(next http.Handler) http.Handler

RequestID generates a unique request identifier and stores it in the request context. The ID is also written as the X-Request-Id response header.

func RequestLogger

func RequestLogger(logger *slog.Logger) func(http.Handler) http.Handler

RequestLogger returns middleware that logs each HTTP request with slog. It records method, path, status, duration, request_id, remote_addr, and user_agent.

func RouteFromContext

func RouteFromContext(ctx context.Context) string

RouteFromContext returns the route template ("/users/{id}") the mux matched for this request, or "" when nothing has matched yet — inside a handler it is always set. It is what the telemetry reports as http.route.

func SecurityHeaders

func SecurityHeaders(next http.Handler) http.Handler

SecurityHeaders sets standard security headers on every response. HSTS is emitted when the request arrives over a direct TLS connection. Behind a TLS-terminating proxy (r.TLS == nil) use the router's WithHSTS option / an `env: production` app so the header is still sent.

func TelemetryMiddleware

func TelemetryMiddleware(next http.Handler) http.Handler

TelemetryMiddleware records OpenTelemetry spans and metrics for HTTP requests.

func TimeoutMiddleware

func TimeoutMiddleware(timeout time.Duration) func(http.Handler) http.Handler

TimeoutMiddleware wraps the stdlib http.TimeoutHandler to cancel requests that exceed the given duration. It automatically skips WebSocket upgrades and requests that accept text/event-stream.

func TimeoutMiddlewareWithExemptions

func TimeoutMiddlewareWithExemptions(timeout time.Duration, exemptPrefixes []string) func(http.Handler) http.Handler

TimeoutMiddlewareWithExemptions is TimeoutMiddleware with URL path prefixes that bypass it. http.TimeoutHandler buffers the response and hides http.Flusher, so a streaming handler behind it cannot flush a byte until it returns; an exempt subtree gets the raw writer and no deadline. A timeout <= 0 disables the middleware for every route.

Types

type CORSOptions

type CORSOptions struct {
	AllowedOrigins   []string
	AllowedMethods   []string
	AllowedHeaders   []string
	ExposedHeaders   []string
	AllowCredentials bool
	MaxAge           int // seconds
}

CORSOptions configures the CORS middleware.

type CSRFOptions

type CSRFOptions struct {
	// ExemptPaths are URL path prefixes that skip CSRF validation (e.g. "/api/").
	ExemptPaths []string
	// CookieName is the name of the CSRF cookie (default: "_csrf").
	// The cookie-prefix names "__Host-…" / "__Secure-…" are supported and
	// recommended over HTTPS: the middleware always issues the cookie with
	// Path=/ and no Domain, so the only prefix precondition it enforces is
	// that InsecureCookie stays false (the prefixes require Secure) —
	// NewCSRFMiddleware/CSRFMiddleware reject the combination.
	CookieName string
	// HeaderName is the HTTP header checked for the token (default: "X-CSRF-Token").
	HeaderName string
	// FormField is the form field name checked for the token (default: "_csrf_token").
	FormField string
	// InsecureCookie disables the Secure flag on CSRF / XSRF cookies.
	// The zero value (false) is the production-safe path: cookies are
	// issued with Secure=true and refuse to ride over plain HTTP. Set
	// this to true only for local-dev plain-HTTP runs. See ADR-008.
	InsecureCookie bool

	// Origin verification options (Laravel-style two-layer approach)
	EnableOriginCheck bool // Enable Sec-Fetch-Site header verification (zero value: false; router.WithCSRF sets it true)
	OriginOnly        bool // Use only origin verification, disable token fallback (zero value: false)
	AllowSameSite     bool // Allow same-site requests in addition to same-origin (zero value: false)

	// Session-based token storage (more secure than cookie)
	UseSessionToken bool   // Store token in session instead of cookie (default: false)
	SessionKey      string // Session key for token storage (default: "csrf_token")

	// X-XSRF-TOKEN encrypted cookie for JavaScript frameworks
	EnableXSRFCookie bool   // Enable encrypted XSRF-TOKEN cookie for JS frameworks (default: false)
	XSRFCookieName   string // XSRF-TOKEN cookie name (default: "XSRF-TOKEN")
	EncryptionKey    []byte // AES-256 key for encrypting XSRF-TOKEN (exactly 32 bytes; see ADR-006 and ADR-008).

	// Token rotation
	RotateToken bool // Regenerate token after each successful validation (default: false)

	// Logger receives WARN-level entries when the server-side encryption
	// of the XSRF-TOKEN cookie fails, and DEBUG-level entries when the
	// incoming X-XSRF-TOKEN header fails to decrypt (browser noise from
	// stale or tampered tokens). When nil, slog.Default() is used. See
	// ADR-008.
	Logger *slog.Logger
}

CSRFOptions configures the CSRF protection middleware.

type Context

type Context struct {
	Writer  http.ResponseWriter
	Request *http.Request
	// contains filtered or unexported fields
}

Context is a unified request context for handlers. It wraps http.ResponseWriter and *http.Request and adds helpers for: - URL/query/form access - sessions - template binding/rendering - typed responses (JSON/XML/file/download)

func NewContext

func NewContext(w http.ResponseWriter, r *http.Request, handlers []Handler, opts ...ContextOption) *Context

NewContext creates a Context from an HTTP request/response pair.

func (*Context) Bind

func (c *Context) Bind(v interface{}) error

Bind decodes request JSON and validates using validate tags.

func (*Context) BindData

func (c *Context) BindData(values map[string]interface{})

BindData merges values into template binding data.

func (*Context) Data

func (c *Context) Data() map[string]interface{}

Data returns a copy of current template binding values.

func (*Context) Download

func (c *Context) Download(path, filename string) error

Download serves a file with attachment content disposition.

func (*Context) File

func (c *Context) File(path string) error

File serves a file as-is.

func (*Context) Form

func (c *Context) Form(name string) string

Form reads a form parameter.

func (*Context) HTML

func (c *Context) HTML(status int, templateName string, data map[string]interface{}) error

HTML renders a named template using merged bound data and call data. Values from data override previously bound keys.

func (*Context) JSON

func (c *Context) JSON(status int, data interface{}) error

JSON writes a JSON response.

func (*Context) Next

func (c *Context) Next() error

Next executes the next handler in the chain.

func (*Context) NoContent

func (c *Context) NoContent() error

NoContent writes a 204 response.

func (*Context) Param

func (c *Context) Param(name string) string

Param reads a path parameter from a route pattern (Go 1.22 path value).

func (*Context) Query

func (c *Context) Query(name string) string

Query reads a query string parameter.

func (*Context) Redirect

func (c *Context) Redirect(status int, location string) error

Redirect sends an HTTP redirect.

func (*Context) SessionDestroy

func (c *Context) SessionDestroy() error

SessionDestroy destroys the current session.

func (*Context) SessionGetBool

func (c *Context) SessionGetBool(key string) bool

SessionGetBool reads a bool value from session.

func (*Context) SessionGetInt

func (c *Context) SessionGetInt(key string) int

SessionGetInt reads an int value from session.

func (*Context) SessionGetString

func (c *Context) SessionGetString(key string) string

SessionGetString reads a string value from session.

func (*Context) SessionManager

func (c *Context) SessionManager() *auth.SessionManager

SessionManager returns the injected session manager.

func (*Context) SessionPutBool

func (c *Context) SessionPutBool(key string, value bool) error

SessionPutBool writes a bool value to session.

func (*Context) SessionPutInt

func (c *Context) SessionPutInt(key string, value int) error

SessionPutInt writes an int value to session.

func (*Context) SessionPutString

func (c *Context) SessionPutString(key, value string) error

SessionPutString writes a string value to session.

func (*Context) SessionRemove

func (c *Context) SessionRemove(key string) error

SessionRemove removes one key from session.

func (*Context) SessionRenewToken

func (c *Context) SessionRenewToken() error

SessionRenewToken renews the current session token.

func (*Context) Set

func (c *Context) Set(key string, value interface{})

Set stores one key/value pair for template binding.

func (*Context) T

func (c *Context) T(key string, args ...any) string

T translates a message key for the request's negotiated locale. It reads the translator and locale the i18n middleware stored on the request context (the framework mounts that middleware when compiled catalogs are found under `locales_path`; see pkg/i18n). Without the middleware it degrades to the key itself, fmt-formatted when args are given — the behaviour of an untranslated application, never an error.

func (*Context) Value

func (c *Context) Value(name string) string

Value returns a parameter from path, then query string, then form data.

func (*Context) XML

func (c *Context) XML(status int, data interface{}) error

XML writes an XML response.

type ContextHandlerFunc

type ContextHandlerFunc = Handler

ContextHandlerFunc is an alias for Handler to maintain backward compatibility.

type ContextOption

type ContextOption func(*Context)

ContextOption configures a Context.

func WithSession

func WithSession(sm *auth.SessionManager) ContextOption

WithSession injects an auth session manager into Context.

func WithTemplates

func WithTemplates(t *template.Template) ContextOption

WithTemplates injects a template engine into Context.

type HTTPError

type HTTPError struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
}

HTTPError represents an error with an associated HTTP status code.

func NewHTTPError

func NewHTTPError(code int, message string) *HTTPError

NewHTTPError creates a new HTTPError.

func (*HTTPError) Error

func (e *HTTPError) Error() string

type Handler

type Handler func(c *Context) error

Handler is a function that processes a request and returns an error.

func FromHTTP

func FromHTTP(h http.HandlerFunc) Handler

FromHTTP adapts a standard http.HandlerFunc to a router.Handler.

func FromHandler

func FromHandler(h http.Handler) Handler

FromHandler adapts a standard http.Handler to a router.Handler.

type Middleware

type Middleware = func(http.Handler) http.Handler

Middleware is a function that wraps an http.Handler with additional behavior.

func WhenMatched

func WhenMatched(gate Middleware) Middleware

WhenMatched wraps a gate so it only judges requests a registered route serves. Where the gate sits, an unmatched request passes through untouched and the mux's 404 (or 405) answers; a matched one goes through the gate as usual. A gate that stepped aside is not forgotten: if a middleware mounted after it rewrites the path onto a registered route, the gate runs anyway — ahead of the next WhenMatched gate that sees the rewritten request, or at dispatch when none follows — in its mounted order and on the path as the gate's own level spells it, so a rewrite cannot turn a miss into an unguarded hit. A gate mounted at the root judges the full path even when the rewrite happened inside a mounted sub-router: policy rows and CSRF exemptions are written against the full path, and the stripped path lives in another namespace (a rewrite of /legacy onto /secret inside Route("/api") is judged as /api/secret, never as /secret). The handler still receives the stripped request, with the context the gates passed down. Outside a Mux there is no routing decision and the gate always runs.

The framework's default-deny authorizer and the CSRF middleware are built with it; a custom gate mounted with Use can be too.

type Mux

type Mux struct {
	// contains filtered or unexported fields
}

Mux wraps http.ServeMux with convenience methods for route registration, middleware chaining, grouping, and sub-router mounting. It serves as a drop-in replacement for chi.Router using only the Go standard library (requires Go 1.22+ for method-aware patterns and path value extraction).

func NewMux

func NewMux() *Mux

NewMux creates a new Mux backed by a fresh http.ServeMux.

func (*Mux) Delete

func (m *Mux) Delete(pattern string, handlers ...Handler)

Delete registers a handler for DELETE requests matching pattern.

func (*Mux) Get

func (m *Mux) Get(pattern string, handlers ...Handler)

Get registers a handler for GET requests matching pattern.

func (*Mux) Group

func (m *Mux) Group(fn func(sub *Mux))

Group creates an inline scope that shares the parent's ServeMux but maintains its own middleware stack. Middlewares added via Use inside the group only apply to routes registered within that group.

func (*Mux) Handle

func (m *Mux) Handle(pattern string, h http.Handler)

Handle registers a handler for all HTTP methods matching pattern.

func (*Mux) HandleFunc

func (m *Mux) HandleFunc(pattern string, h http.HandlerFunc)

HandleFunc registers a HandlerFunc for all HTTP methods matching pattern.

func (*Mux) Mount

func (m *Mux) Mount(pattern string, handler http.Handler)

Mount registers handler under the given pattern prefix. Requests matching the prefix are forwarded to handler with the prefix stripped. If pattern does not end with "/", a trailing slash is appended so that the ServeMux treats it as a subtree pattern.

func (*Mux) Patch

func (m *Mux) Patch(pattern string, handlers ...Handler)

Patch registers a handler for PATCH requests matching pattern.

func (*Mux) Post

func (m *Mux) Post(pattern string, handlers ...Handler)

Post registers a handler for POST requests matching pattern.

func (*Mux) Put

func (m *Mux) Put(pattern string, handlers ...Handler)

Put registers a handler for PUT requests matching pattern.

func (*Mux) Resource

func (m *Mux) Resource(pattern string, handlers ResourceHandlers)

Resource registers a conventional REST route set for one resource prefix: - GET /<resource>/ -> List - POST /<resource>/ -> Create - GET /<resource>/{id} -> Retrieve - PUT /<resource>/{id} -> Update - DELETE /<resource>/{id} -> Delete

Example:

r.Resource("/users", router.ResourceHandlers{ ... })

func (*Mux) Route

func (m *Mux) Route(pattern string, fn func(sub *Mux))

Route creates a sub-router mounted under the given pattern prefix. The sub- router has its own middleware stack and its own route namespace, and inherits the parent's session manager and template engine (QCD-FW-8).

func (*Mux) ServeHTTP

func (m *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP dispatches the request through the middleware chain and into the underlying ServeMux.

func (*Mux) SetHTMLTemplates

func (m *Mux) SetHTMLTemplates(t *template.Template)

SetHTMLTemplates sets the template engine for the Mux and its sub-routers.

func (*Mux) SetSessionManager

func (m *Mux) SetSessionManager(sm *auth.SessionManager)

SetSessionManager sets the session manager for the Mux and its sub-routers.

func (*Mux) Static

func (m *Mux) Static(pattern, root string)

Static registers a handler to serve static files from root directory under the given pattern prefix.

func (*Mux) Use

func (m *Mux) Use(mws ...Middleware)

Use appends one or more middlewares to the Mux's middleware stack. For top-level Mux instances, middlewares are applied via ServeHTTP to all requests. For Group scopes, middlewares wrap individual handlers at registration time.

func (*Mux) Walk

func (m *Mux) Walk(fn func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error) error

Walk iterates over all registered routes, calling fn for each one. The signature is compatible with the chi.Walk callback API so that callers can migrate without code changes beyond the call site.

func (*Mux) With

func (m *Mux) With(mws ...Middleware) *Mux

With adds a list of middlewares to an inline sub-router and returns it.

type Option

type Option func(*routerOpts)

Option configures a Router during creation.

func WithCORSCredentials

func WithCORSCredentials(allow bool) Option

WithCORSCredentials controls whether the CORS middleware emits Access-Control-Allow-Credentials: true. It defaults to false (SEC-1, security-by-default): credentialed cross-origin responses are only emitted when the app explicitly opts in with WithCORSCredentials(true). Because the Fetch standard forbids combining credentials with the `*` wildcard — and reflecting every Origin with credentials is itself unsafe — credentials must be paired with an explicit origin allow-list (WithCORSOrigins). Enabling credentials without an allow-list is a misconfiguration.

func WithCORSOrigins

func WithCORSOrigins(origins ...string) Option

WithCORSOrigins sets allowed CORS origins. An empty list allows all origins.

func WithCSRF

func WithCSRF(exemptPaths ...string) Option

WithCSRF enables CSRF protection middleware.

func WithCSRFInsecureCookie

func WithCSRFInsecureCookie(insecure bool) Option

WithCSRFInsecureCookie disables the Secure attribute on the CSRF cookies. Development-only opt-out, mirroring the session cookie posture: with the default (Secure) cookie, the double-submit flow is unreachable for plain HTTP clients such as Go's cookiejar over http://127.0.0.1 — browsers special-case localhost as trustworthy, non-browser clients do not.

func WithDevelopmentErrors

func WithDevelopmentErrors(enabled bool) Option

WithDevelopmentErrors controls what a handler error that is neither a DomainError nor an HTTPError puts on the wire. Off — the default — the response is a 500 with a generic body and the error itself goes to the log, with the request id so the two can be joined; a driver's message or a file path belongs in the log, not in a browser. On, meant for `env: development`, the body carries err.Error() so a developer sees the cause without opening the log. app.New sets it from the environment.

func WithHSTS

func WithHSTS(enabled bool) Option

WithHSTS makes the security-headers middleware emit Strict-Transport-Security on every response, not only over a direct TLS connection. Enable it in production (typically behind a TLS-terminating proxy, where r.TLS is nil): app.New wires it from `env: production`. Leave it off in development so plain-HTTP local runs are not pinned to HTTPS.

func WithRateLimit

func WithRateLimit(requests int, window time.Duration) Option

WithRateLimit enables in-process request rate limiting. Requests <= 0 disables the limiter.

func WithRateLimitPolicy

func WithRateLimitPolicy(policy RateLimitPolicy) Option

WithRateLimitPolicy enables in-process request rate limiting with optional burst, route-level, and role-level dimensions.

func WithTimeout

func WithTimeout(seconds int) Option

WithTimeout sets the request timeout in seconds. Zero or negative disables the timeout middleware entirely; prefer WithTimeoutExempt to carve out the handful of routes that stream (SSE, long polls, large downloads) and keep the timeout on everything else.

func WithTimeoutExempt

func WithTimeoutExempt(paths ...string) Option

WithTimeoutExempt excludes URL path prefixes from the request timeout. http.TimeoutHandler buffers the whole response and hides http.Flusher, so a streaming handler behind it can never flush: exempt its subtree and the handler gets the raw writer, with Flush and Hijack, and no deadline. Requests whose Accept header asks for text/event-stream are exempt without listing them.

func WithTrustedProxies

func WithTrustedProxies(proxies ...string) Option

WithTrustedProxies sets the allow-list of upstream proxy addresses (IPs or CIDR ranges) whose X-Forwarded-For / X-Real-IP headers the RealIP middleware is allowed to honor. With no trusted proxies (the default) forwarding headers are ignored and r.RemoteAddr — the immediate peer — is the client IP, which prevents header-spoofed rate-limit evasion and audit poisoning.

type RateLimitOptions

type RateLimitOptions struct {
	Requests       int
	Window         time.Duration
	Burst          int
	ScopeByRoute   bool
	ScopeByRole    bool
	KeyFunc        func(*http.Request) string
	RouteDimension func(*http.Request) string
	RoleDimension  func(*http.Request) string
}

RateLimitOptions configures rate limiting middleware.

type RateLimitPolicy

type RateLimitPolicy struct {
	Requests int
	Window   time.Duration
	Burst    int
	ByRoute  bool
	ByRole   bool
}

RateLimitPolicy describes advanced limiter dimensions.

type ResourceHandlers

type ResourceHandlers struct {
	List     Handler
	Create   Handler
	Retrieve Handler
	Update   Handler
	Delete   Handler
}

ResourceHandlers groups CRUD handlers for one REST resource. Nil handlers are skipped.

type RouteEntry

type RouteEntry struct {
	Method      string
	Pattern     string
	Middlewares int
	// contains filtered or unexported fields
}

RouteEntry represents a registered route for introspection via Walk.

type Router

type Router struct {
	*Mux
	// contains filtered or unexported fields
}

Router wraps a Mux with Nucleus conventions and a default middleware stack.

func New

func New(logger *slog.Logger, opts ...Option) *Router

New creates a Router with the default middleware stack already applied.

type WrapResponseWriter

type WrapResponseWriter struct {
	http.ResponseWriter
	// contains filtered or unexported fields
}

WrapResponseWriter is a response writer wrapper that captures the HTTP status code and the number of bytes written. It replaces chi/middleware's equivalent.

func NewWrapResponseWriter

func NewWrapResponseWriter(w http.ResponseWriter, _ int) *WrapResponseWriter

NewWrapResponseWriter creates a new WrapResponseWriter. The protoMajor argument is accepted for API compatibility but currently unused.

func (*WrapResponseWriter) BytesWritten

func (w *WrapResponseWriter) BytesWritten() int

BytesWritten returns the total bytes written to the response body.

func (*WrapResponseWriter) Flush

func (w *WrapResponseWriter) Flush()

Flush implements http.Flusher if the underlying writer supports it.

func (*WrapResponseWriter) Hijack

func (w *WrapResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error)

Hijack implements http.Hijacker if the underlying writer supports it.

func (*WrapResponseWriter) Status

func (w *WrapResponseWriter) Status() int

Status returns the HTTP status code that was written.

func (*WrapResponseWriter) Unwrap

Unwrap returns the underlying ResponseWriter for middleware compatibility.

func (*WrapResponseWriter) Write

func (w *WrapResponseWriter) Write(b []byte) (int, error)

func (*WrapResponseWriter) WriteHeader

func (w *WrapResponseWriter) WriteHeader(code int)

func (*WrapResponseWriter) WroteHeader

func (w *WrapResponseWriter) WroteHeader() bool

WroteHeader reports whether the status line has been written (via an explicit WriteHeader or an implicit first Write). Recoverer consults it to avoid a second WriteHeader after a mid-response panic.

Directories

Path Synopsis
Package interceptor is the contract a third-party request interceptor implements — and nothing else.
Package interceptor is the contract a third-party request interceptor implements — and nothing else.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL