Documentation
¶
Index ¶
- func CORS(cfg CORSConfig) func(http.Handler) http.Handler
- func PermissiveCSP(next http.Handler) http.Handler
- func Recoverer(onPanic func(r *http.Request, recovered any, stack []byte)) func(http.Handler) http.Handler
- func SecurityHeaders(next http.Handler) http.Handler
- func SelectiveCompress(skip func(r *http.Request) bool, level int, types ...string) func(http.Handler) http.Handler
- func StrictCSP(next http.Handler) http.Handler
- type CORSConfig
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func CORS ¶
func CORS(cfg CORSConfig) func(http.Handler) http.Handler
CORS returns a middleware that emits cross-origin headers for allowed origins and answers preflight OPTIONS requests directly. ALL per-request choices are precomputed here at boot (origin set, header strings) — the request path only does a map lookup and a few header writes, and a request WITHOUT an Origin header (every server-to-server / curl / mobile call) returns on the first line untouched, so non-browser traffic pays effectively nothing.
Placement: this MUST run BEFORE the tenant, response-cache, JWT and RBAC middlewares. (1) A preflight carries no credentials and no tenant, so it must be answered before JWT (which would 401 it) and before the tenant resolver (which could 400/500 a bare Host). (2) The response cache stores only status, body, Content-Type and ETag — never Access-Control-* — so a per-request Allow-Origin set by THIS outer middleware is never cached and replayed to a different origin.
func PermissiveCSP ¶
PermissiveCSP sets a CSP suitable for the GraphiQL IDE playground. GraphiQL loads scripts and styles from CDN, so it needs a relaxed policy. Apply only to /graphiql in development mode.
func Recoverer ¶
func Recoverer(onPanic func(r *http.Request, recovered any, stack []byte)) func(http.Handler) http.Handler
Recoverer returns middleware that recovers a panic in any downstream handler or middleware and turns it into a clean JSON 500, so ONE request's panic never takes the process down. It is the request-chain half of the Phase-0 safety model (LIBRARY-HARDEN-S1) — the goroutine half is Ctx.SafeGo, because Go's recover() only reaches its OWN goroutine (a bare `go func(){panic()}()` in a handler crashes every tenant; this middleware cannot save that — SafeGo does).
onPanic (may be nil) is called with the request, the recovered value and the captured stack BEFORE the response is written — wire it to a metric counter and a structured log. It mirrors chi's Recoverer (no ResponseWriter wrapping, so the steady-state cost is a single deferred recover — identical to before) and re-panics on http.ErrAbortHandler so net/http can abort the connection.
func SecurityHeaders ¶
SecurityHeaders adds defensive HTTP response headers to every response. Register this as the outermost middleware in the chain.
func SelectiveCompress ¶
func SelectiveCompress(skip func(r *http.Request) bool, level int, types ...string) func(http.Handler) http.Handler
SelectiveCompress is chi's Compress middleware with a per-request skip — the same shape as the response cache's path bypass (pkg/cache): most requests flow through the compressor, the skipped ones reach the next handler with the ResponseWriter UNWRAPPED.
Why it exists (FILES-BENCH finding, fixed in FILES-FIX-SENDFILE): chi's compressResponseWriter wraps EVERY response — even content types it never compresses — and does not implement io.ReaderFrom, so http.ServeContent's io.CopyN could not reach TCPConn.ReadFrom and file downloads fell back to a userspace copy loop instead of sendfile zero-copy (measured: 0 sendfile calls, 53% of nginx throughput at ~5.5× the CPU/byte). Skipping the wrapper on byte-serving routes restores the kernel fast path — and skipping compression on binary blobs is correct on its own terms (the FILES-V2 investigation: never compress images/video/binaries).
skip is consulted per request; everything else keeps the exact chimiddleware.Compress behavior (level + compressible content types).
Types ¶
type CORSConfig ¶
type CORSConfig struct {
// AllowedOrigins is the exact origin allowlist (scheme + host + optional port,
// e.g. "https://app.example.com"). The single literal "*" allows ANY origin.
AllowedOrigins []string
// AllowedMethods is echoed in the preflight Access-Control-Allow-Methods.
// Empty ⇒ GET, POST, PUT, PATCH, DELETE, OPTIONS.
AllowedMethods []string
// AllowedHeaders is echoed in the preflight Access-Control-Allow-Headers.
// Empty ⇒ Authorization, Content-Type.
AllowedHeaders []string
// ExposedHeaders lists response headers a browser script may read
// (Access-Control-Expose-Headers). Empty ⇒ none beyond the CORS-safelisted set.
ExposedHeaders []string
// AllowCredentials, when true, sends Access-Control-Allow-Credentials: true so a
// browser may send cookies / Authorization. Per the Fetch spec a credentialed
// response may NOT use the literal "*" origin, so when AllowCredentials is set
// and AllowedOrigins is "*", the request's Origin is REFLECTED instead.
AllowCredentials bool
// MaxAge bounds how long (seconds) a browser may cache a preflight result.
// 0 ⇒ 600 (10 minutes). Negative ⇒ no Access-Control-Max-Age header.
MaxAge int
}
CORSConfig configures cross-origin resource sharing for the browser-facing data-plane routes (/api, /auth, /graphql, /openapi). CORS is INFRASTRUCTURE configuration of the instance — which browser origins may call this engine — NOT part of the schema (the schema describes the data model, identical for every tenant; the allowed origins are a deployment decision). The zero value (no AllowedOrigins) disables CORS entirely: the engine emits no Access-Control-* header and short-circuits no preflight — the safe default. An operator opts in explicitly by listing origins (APPXIMO_CORS_ORIGINS), so a browser SPA on another origin never works by accident, only by configuration.
func (CORSConfig) Enabled ¶
func (c CORSConfig) Enabled() bool
Enabled reports whether any origin is configured. A CORSConfig with no origins is a no-op and the engine does not wire the middleware at all.