Help us shape the future of Go! Take the 2026 Go Developer Survey

middleware

package
v0.30.0 Latest Latest
Warning

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

Go to latest
Published: Oct 19, 2026 License: MIT Imports: 31 Imported by: 1

Documentation

Overview

Package middleware provides Vukem's built-in net/http middleware. Every constructor returns a func(http.Handler) http.Handler, so they compose with any other middleware. Errors are rendered through web.Fail, which uses the router's error handler (problem+json, HTML or htmx fragments).

The set covers Recover, RequestID, Logger, Timeout, BodyLimit, MethodOverride, JSONOnly, SecureHeaders (CSP with nonces, HSTS), CORS, Gzip, Static and Maintenance. vukem.DefaultMiddleware installs the secure web stack; vukem.APIMiddleware the API one.

See https://github.com/AlexGalhardo/vukem/blob/main/docs/http.md.

Example
package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"

	"github.com/AlexGalhardo/vukem/middleware"
)

func main() {
	hello := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		fmt.Fprint(w, "hello")
	})

	// Middleware are plain func(http.Handler) http.Handler values, so they
	// compose by hand or through router.Use.
	h := middleware.Recover()(
		middleware.RequestID()(
			middleware.SecureHeaders(middleware.SecureHeadersConfig{})(hello),
		),
	)

	rec := httptest.NewRecorder()
	h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/", http.NoBody))
	fmt.Println(rec.Code, rec.Body.String())
	fmt.Println(rec.Header().Get("X-Frame-Options"), rec.Header().Get("X-Content-Type-Options"))
	fmt.Println(rec.Header().Get("X-Request-Id") != "")
}
Output:
200 hello
DENY nosniff
true

Index

Examples

Constants

View Source
const DefaultCSP = "default-src 'self'; script-src 'self' 'nonce-{nonce}'; style-src 'self' 'nonce-{nonce}'; " +
	"img-src 'self' data:; font-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'self'; " +
	"frame-ancestors 'none'; form-action 'self'"

DefaultCSP is a strict policy suitable for server-rendered pages with nonce'd scripts and styles.

View Source
const DefaultMaintenanceFile = "storage/framework/down"

DefaultMaintenanceFile is where `vukem down` writes the maintenance state.

View Source
const MaintenanceCookie = "vukem_maintenance"

MaintenanceCookie carries the bypass granted by visiting /<secret>.

View Source
const RequestIDHeader = "X-Request-ID"

RequestIDHeader is the header carrying the request id.

Variables

This section is empty.

Functions

func CSPNonce

func CSPNonce(ctx context.Context) string

CSPNonce returns the per-request CSP nonce, or "" when SecureHeaders did not run. Templates expose it as {{ cspNonce }}.

func WriteMaintenance added in v0.18.0

func WriteMaintenance(file string, m MaintenanceState) error

WriteMaintenance puts the app in maintenance mode (vukem down).

Types

type CORSConfig

type CORSConfig struct {
	// AllowedOrigins lists exact origins ("https://app.example.com") or "*".
	AllowedOrigins []string
	// AllowedMethods defaults to GET, HEAD, POST, PUT, PATCH, DELETE.
	AllowedMethods []string
	// AllowedHeaders defaults to Accept, Authorization, Content-Type,
	// X-Requested-With, X-CSRF-Token.
	AllowedHeaders []string
	// ExposedHeaders are readable by the browser.
	ExposedHeaders []string
	// AllowCredentials allows cookies/Authorization; incompatible with "*".
	AllowCredentials bool
	// MaxAge caches preflight responses.
	MaxAge time.Duration
}

CORSConfig configures cross-origin resource sharing. The zero value allows no cross-origin requests (deny by default).

type MaintenanceConfig added in v0.18.0

type MaintenanceConfig struct {
	// File is the state file (default DefaultMaintenanceFile).
	File string
	// Key signs bypass cookies (derive it from APP_KEY). Without a key the
	// secret bypass is disabled: an unkeyed HMAC would be forgeable.
	Key []byte
	// PreviousKeys still accept bypass cookies signed before an APP_KEY
	// rotation (derive them from APP_PREVIOUS_KEYS); such a cookie is
	// re-issued under Key.
	PreviousKeys [][]byte
	// Except lists paths always served (default "/up").
	Except []string
	// Secure marks the bypass cookie Secure (production, HTTPS).
	Secure bool
}

MaintenanceConfig configures Maintenance.

type MaintenanceState added in v0.18.0

type MaintenanceState struct {
	// Secret, when set, lets visitors of /<secret> through (stored hashed).
	Secret string `json:"-"`
	// Retry is sent as Retry-After.
	Retry time.Duration `json:"-"`

	SecretHash string    `json:"secret_hash,omitempty"`
	RetryAfter int       `json:"retry_after,omitempty"`
	Since      time.Time `json:"since"`
}

MaintenanceState is the state written by `vukem down`.

type Middleware

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

Middleware wraps an http.Handler.

func BodyLimit

func BodyLimit(n int64) Middleware

BodyLimit caps request bodies at n bytes; reading a larger body fails with *http.MaxBytesError, which the web helpers (Input, BindJSON, Parse, uploads) answer with 413. A declared Content-Length over the limit fails on the first read, before any byte is consumed.

A later BodyLimit, web.LimitBody or upload.Rules.MaxSize on the same request replaces the limit instead of nesting under it, so the router-wide default set by vukem.DefaultMiddleware can be raised or lowered per route. The check runs when the body is read, against the innermost limit: a handler that never reads the body gets no 413.

Example
package main

import (
	"fmt"
	"log/slog"
	"net/http"
	"net/http/httptest"
	"strings"

	"github.com/AlexGalhardo/vukem/middleware"
	"github.com/AlexGalhardo/vukem/web"
)

func main() {
	h := middleware.BodyLimit(16)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		c := web.NewCtx(w, r, &web.Services{Log: slog.New(slog.DiscardHandler)})

		var v map[string]any
		if err := c.BindJSON(&v); err != nil {
			c.HandleError(err)
			return
		}

		_ = c.NoContent()
	}))

	// Reading a body over the limit fails with 413. A declared
	// Content-Length over it fails on the first read, before any byte is
	// consumed; undeclared bodies are cut off while reading.
	req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(`{"a":"`+strings.Repeat("x", 64)+`"}`))
	req.Header.Set("Content-Type", "application/json")
	rec := httptest.NewRecorder()
	h.ServeHTTP(rec, req)
	fmt.Println(rec.Code)
}
Output:
413

func CORS

func CORS(cfg CORSConfig) (Middleware, error)

CORS returns CORS middleware. Register it as server middleware so preflight requests are answered before routing. It returns an error for insecure configurations ("*" with credentials), so misconfiguration fails at boot (A02).

func Gzip

func Gzip() Middleware

Gzip compresses text-like responses for clients that accept gzip. Bodies smaller than 1 KiB, already-encoded responses, ranges and event streams are passed through.

func JSONOnly

func JSONOnly() Middleware

JSONOnly makes a route group JSON-only: write requests must send JSON (415 otherwise) and errors render as problem+json.

func Logger

func Logger(log *slog.Logger) Middleware

Logger logs one structured line per request (method, path, status, duration, size, request id, peer address and client IP). peer is the direct connection (RemoteAddr host); ip is the client resolved through web.Services.TrustedProxies, as c.IP() returns it. 5xx log at ERROR, 4xx at WARN.

func Maintenance added in v0.18.0

func Maintenance(cfg MaintenanceConfig) Middleware

Maintenance answers 503 (Retry-After, the "errors/503" view or problem+json) while the state file exists. Visiting /<secret> sets a signed bypass cookie. A file that cannot be read keeps the app down.

func MethodOverride

func MethodOverride() Middleware

MethodOverride lets HTML forms send PUT, PATCH and DELETE through a hidden "_method" field on POST. Multipart forms put it in the action's query string instead ("?_method=PUT"), so their body is never buffered. Register it as server middleware so routing sees the overridden method.

func Recover

func Recover() Middleware

Recover converts panics into 500 responses rendered by the error handler and logs the stack trace server-side only (A10). http.ErrAbortHandler is re-raised so net/http can abort the connection.

func RequestID

func RequestID() Middleware

RequestID reuses a well-formed incoming X-Request-ID or generates one, stores it in the context (web.RequestID) and echoes it in the response.

func SecureHeaders

func SecureHeaders(cfg SecureHeadersConfig) Middleware

SecureHeaders sets security headers on every response (A02).

func Static

func Static(root *os.Root, cfg StaticConfig) Middleware

Static serves files from root when they exist and passes other requests on. Files are opened through os.Root, so ".." and symlinks cannot escape the directory (A01); dotfiles and directory listings are never served. The caller owns root and closes it on shutdown.

Files under ImmutablePrefixes ("/build/" by default) and fingerprinted names get a one-year immutable cache; every other file must revalidate. A name is fingerprinted when its last "-" or "." segment before the extension is 8+ lowercase hex characters with a digit and a letter (app-3f9a1c2b.js, app.4f2a9c1e.css) or 8 base64url characters with a digit, an upper and a lower case letter (Vite's index-BkS3pZ8_.js); hero-mountains.jpg or report-20240101.pdf are not. Revalidation uses Last-Modified, or an ETag (a content hash) for files without a modification time, such as embed.FS files.

func StaticFS

func StaticFS(fsys fs.FS, cfg StaticConfig) Middleware

StaticFS is Static over any fs.FS (e.g. an embed.FS in production).

func Timeout

func Timeout(d time.Duration) Middleware

Timeout sets a deadline on the request context. Handlers and queries that respect the context stop when it expires.

type SecureHeadersConfig

type SecureHeadersConfig struct {
	// CSP is the Content-Security-Policy; "{nonce}" is replaced with a
	// per-request nonce. Empty uses DefaultCSP; "-" disables the header.
	CSP string
	// HSTSMaxAge enables Strict-Transport-Security on HTTPS requests.
	// Zero uses two years; negative disables it.
	HSTSMaxAge time.Duration
	// HSTSForce sends Strict-Transport-Security on every response that
	// reaches SecureHeaders, also on requests that look like plain HTTP
	// (middleware installed before it, such as Maintenance's 503, answers
	// without it). Use it only when every public
	// request reaches a TLS-terminating proxy whose range cannot be
	// trusted (so TrustForwardedProto cannot be used): it asserts the
	// public origin is HTTPS-only instead of checking each request.
	// Browsers ignore HSTS received over plain HTTP (RFC 6797 section 8.1).
	HSTSForce bool
	// FrameOptions defaults to DENY; "-" disables it.
	FrameOptions string
	// ReferrerPolicy defaults to strict-origin-when-cross-origin.
	ReferrerPolicy string
	// PermissionsPolicy defaults to camera=(), microphone=(), geolocation=().
	PermissionsPolicy string
	// TrustForwardedProto treats X-Forwarded-Proto: https as HTTPS (set it
	// only behind a trusted proxy).
	TrustForwardedProto bool
	// TrustedProxies, when non-empty, limits TrustForwardedProto to
	// requests whose direct peer is in one of these networks, so clients
	// that reach the app around the proxy cannot claim HTTPS.
	TrustedProxies []*net.IPNet
	// DevServer returns the origin of a running asset dev server (the Vite
	// hot URL) or "". When set, the origin is allowed in script-src,
	// style-src, img-src, font-src and connect-src (plus its ws:// form for
	// hot reload). Wire it only outside production.
	DevServer func() string
	// CSPSources adds sources to directives of the policy, e.g. an
	// analytics origin in script-src and connect-src, or the 'sha256-...'
	// hash of a style a library injects. A "-src" directive missing from
	// the policy starts from the default-src sources. Unsafe sources
	// ('unsafe-*' keywords, "*", nonces, scheme-only sources in script
	// directives) are refused: write a full CSP to opt into them.
	CSPSources map[string][]string
	// ReportURI receives CSP violation reports: an absolute http(s) URL or
	// a path. It adds report-uri and report-to directives and a
	// Reporting-Endpoints header naming the "csp-endpoint" group.
	ReportURI string
}

SecureHeadersConfig configures SecureHeaders. Zero values use the secure defaults documented in docs/security.md.

func (SecureHeadersConfig) Validate added in v0.24.0

func (cfg SecureHeadersConfig) Validate() error

Validate reports TrustForwardedProto without TrustedProxies, HSTSForce with HSTS disabled, and CSPSources and ReportURI values that SecureHeaders would drop. Call it at boot: SecureHeaders never fails, it serves the policy without the invalid extras (stricter, never weaker).

type StaticConfig

type StaticConfig struct {
	// Prefix is the URL prefix ("/" serves files at the root).
	Prefix string
	// ImmutablePrefixes get a one-year immutable cache (hashed build output).
	// Elsewhere only fingerprinted names (see Static) are immutable.
	ImmutablePrefixes []string
}

StaticConfig configures Static.

Jump to

Keyboard shortcuts

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