Add middleware
Named or inline
Section titled “Named or inline”// Named middleware is owned by the service that registers it, which is what// lets a teardown disable it everywhere it is referenced.func register(router *gas.Router) { router.Register("auth", "require-auth", func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // validate a token, then: next.ServeHTTP(w, r) }) })}func apply(router *gas.Router) { // Globally, by name. router.UseMiddlewareByName("require-auth")
// Globally, inline. An inline func has no owner and survives any teardown. router.UseMiddlewareFunc(func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { next.ServeHTTP(w, r) }) })}The distinction matters more than it looks. Named middleware is owned by the service that registered it, so a teardown of that service disables it everywhere it is referenced. Inline middleware has no owner and survives any teardown. See Ownership and teardown.
UseMiddlewareByName panics if the name is not registered, which is deliberate:
a typo should not silently leave a route unguarded.
Request logging
Section titled “Request logging”router.UseMiddlewareFunc(gas.RequestLogger[gas.Logger]())Logs method, path, status, bytes, duration, and remote address, at error level
for statuses at 400 and above. Needs a scoped gas.Logger in the container.
Covered in Structured logging.
Security headers
Section titled “Security headers”// Secure defaults out of the box; override individually, or pass an empty// string to drop a header entirely.func security(router *gas.Router) { router.UseMiddlewareFunc(gas.SecurityHeaders( gas.WithSecurityHeadersFrameOptions("SAMEORIGIN"), gas.WithSecurityHeadersContentSecurityPolicy("default-src 'self'"), ))}Applied by default:
| Header | Default |
|---|---|
X-Content-Type-Options |
nosniff |
X-Frame-Options |
DENY |
Referrer-Policy |
strict-origin-when-cross-origin |
Permissions-Policy |
camera=(), microphone=(), geolocation=() |
Emitted only when you configure them, because a wrong value is worse than none:
Content-Security-Policy, Strict-Transport-Security,
Cross-Origin-Opener-Policy, and Cross-Origin-Resource-Policy.
Cache-Control
Section titled “Cache-Control”func caching(router *gas.Router) { // Fingerprinted assets: cache hard. router.UseMiddlewareFunc(gas.CacheControl( gas.WithCacheControlPathPrefix("/static/"), gas.WithCacheControlPublic(), gas.WithCacheControlMaxAge(365*24*time.Hour), gas.WithCacheControlImmutable(), ))
// API responses: never store. router.UseMiddlewareFunc(gas.CacheControl( gas.WithCacheControlPathPrefix("/api/"), gas.WithCacheControlNoStore(), ))}Path filters accept exact paths, prefixes, and suffixes, in singular and plural forms. With no filters the header applies to every request; with no directives the middleware passes through without setting anything.
Cross-origin protection
Section titled “Cross-origin protection”Gas enables Go’s
http.CrossOriginProtection
by default. Non-safe cross-origin browser requests, POST, PUT, PATCH, DELETE,
are rejected unless the origin is trusted. Safe methods always pass, and
requests with no Sec-Fetch-Site or Origin header, such as curl and
server-to-server calls, are allowed through.
Same-origin apps need no configuration. Anything else does:
// Cross-origin protection is on by default. Add the front-ends you trust.func csrf() []gas.AppOption { return []gas.AppOption{ gas.WithTrustedOrigin("https://app.example.com"),
// Webhook receivers validate their own signatures. gas.WithCSRFInsecureBypassPattern("/webhooks/stripe"),
gas.WithCSRFDenyHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { _ = gas.WriteError(w, r, gas.Forbidden("cross-origin request denied")) })), }}A deny handler runs before the request scope exists, so resolve nothing from DI
inside it. gas.WriteError is safe there: it renders the unified error shape
without logging or touching the scope.
Ordering
Section titled “Ordering”Middleware runs in registration order, outermost first. Global middleware wraps group middleware, which wraps per-route middleware. Register logging early so it observes everything downstream, and authentication before anything that assumes a principal is present.