Production-ready middleware for CORS, compression, security headers, logging, panic recovery, and more.
Middleware in ALOS wraps handlers to add pre/post processing. The signature is:
type MiddlewareFunc func(HandlerFunc) HandlerFunc
Apply middleware globally with Use() or per-group:
s.Router.Use(core.Recovery()) s.Router.Use(core.Logger()) api := s.Router.Group("/api", core.RequestID()) s.Router.Build()
Middleware executes in the order registered. The chain is pre-computed at build time for zero per-request overhead.
Catches panics and returns 500 with stack trace logging.
Logs method, path, status code, and response time.
Cross-origin resource sharing with hot-reload config.
HSTS, X-Frame-Options, XSS protection, CSP referrer.
gzip/deflate response compression with min-size threshold.
Extracts client IP from X-Forwarded-For / X-Real-IP.
Adds a unique X-Request-ID header to every request.
Aborts handlers after a deadline and returns 504.
Rejects request bodies exceeding a byte threshold (413).
Restricts routes to specific HTTP methods (405).
HTTP Basic Authentication with constant-time comparison.
Injects a static response header on every request.
Apply middleware only when a predicate returns true.
Catches panics in handlers and returns a 500 Internal Server Error instead of
crashing the server:
s.Router.Use(core.Recovery()) s.Router.GET("/panic", func(req *core.Request, resp *core.Response) { panic("something went wrong") })
Always register Recovery() first. It must wrap all other middleware to catch panics from any layer in the chain.
Logs each request with method, path, status code, and duration:
s.Router.Use(core.Logger())
ALOS provides a runtime-swappable CORS engine. Configure origins, methods, and headers:
s.SetCORS(core.CORSConfig{ AllowOrigins: []string{"https://app.example.com", "https://admin.example.com"}, AllowMethods: []string{"GET", "POST", "PUT", "DELETE"}, AllowHeaders: []string{"Content-Type", "Authorization"}, ExposeHeaders: []string{"X-Request-ID"}, AllowCredentials: true, MaxAge: 3600, }) s.Router.Use(s.CORS.Middleware())
Update CORS configuration at runtime without restarting. Uses lock-free atomic snapshots:
s.CORS.Update(core.CORSConfig{ AllowOrigins: []string{"*"}, AllowMethods: []string{"GET"}, })
| Option | Type | Description |
|---|---|---|
AllowOrigins |
[]string |
Allowed origins. Use "*" for all. |
AllowMethods |
[]string |
Allowed HTTP methods. |
AllowHeaders |
[]string |
Allowed request headers. |
ExposeHeaders |
[]string |
Headers visible to the browser. |
AllowCredentials |
bool |
Allow cookies/auth headers. |
MaxAge |
int |
Preflight cache duration (seconds). |
Add essential security headers to all responses:
s.Router.Use(core.SecurityHeaders(core.DefaultSecurityHeaders())) s.Router.Use(core.SecurityHeaders(core.SecurityHeadersConfig{ ContentTypeNosniff: true, XFrameOptions: "DENY", XSSProtection: true, HSTSMaxAge: 63072000, HSTSSubdomains: true, HSTSPreload: true, ReferrerPolicy: "strict-origin-when-cross-origin", }))
This adds the following headers to every response:
X-Content-Type-Options: nosniffX-Frame-Options: DENYX-XSS-Protection: 1; mode=blockStrict-Transport-Security: max-age=63072000; includeSubDomains; preload
Referrer-Policy: strict-origin-when-cross-originAutomatically compress responses with gzip or deflate based on Accept-Encoding:
s.Router.Use(core.Compress(core.CompressConfig{ Level: 6, MinSize: 512, }))
Responses below MinSize bytes are sent uncompressed to avoid overhead on small
payloads.
Aborts handler execution after a deadline and returns 504 Gateway Timeout:
s.Router.Use(core.Timeout(5 * time.Second))
Apply different timeouts per route group:
api := s.Router.Group("/api", core.Timeout(10 * time.Second)) uploads := s.Router.Group("/upload", core.Timeout(60 * time.Second))
Timeout applies to the total handler execution time, not to request reading or response writing.
Rejects requests with bodies larger than the specified byte limit with
413 Request Entity Too Large:
s.Router.Use(core.BodyLimit(10 * 1024 * 1024))
Different limits per group:
api := s.Router.Group("/api", core.BodyLimit(1 * 1024 * 1024)) uploads := s.Router.Group("/upload", core.BodyLimit(100 * 1024 * 1024))
Restricts a route group to specific HTTP methods. Returns 405 Method Not Allowed
for anything else:
readOnly := s.Router.Group("/public", core.AllowMethods("GET", "HEAD"), ) readOnly.GET("/info", infoHandler) readOnly.GET("/status", statusHandler)
Combine with other middleware:
admin := s.Router.Group("/admin", core.AllowMethods("GET", "POST"), core.BasicAuth(core.BasicAuthConfig{ Users: map[string]string{"admin": "secret"}, }), )
HTTP Basic Authentication. Uses constant-time password comparison to prevent timing attacks.
Returns 401 with WWW-Authenticate header on failure:
s.Router.Use(core.BasicAuth(core.BasicAuthConfig{ Users: map[string]string{ "admin": "supersecret", "readonly": "viewonly", }, Realm: "Admin Panel", }))
Protect only specific groups:
admin := s.Router.Group("/admin", core.BasicAuth(core.BasicAuthConfig{ Users: map[string]string{"admin": "password"}, Realm: "Admin", })) admin.GET("/dashboard", dashboardHandler) admin.POST("/settings", settingsHandler)
| Field | Type | Description |
|---|---|---|
Users |
map[string]string |
Username → password pairs |
Realm |
string |
Realm shown in the browser prompt |
When behind a reverse proxy, extract the real client IP from proxy headers:
s.Router.Use(core.RealIP()) s.Router.GET("/ip", func(req *core.Request, resp *core.Response) { resp.String(req.RemoteAddr) })
Checks X-Forwarded-For and X-Real-IP headers (in that order).
Adds a unique X-Request-ID header to every request for distributed tracing:
s.Router.Use(core.RequestID()) s.Router.GET("/debug", func(req *core.Request, resp *core.Response) { id := req.Header("X-Request-ID") resp.String(id) })
Injects a static response header on every request:
s.Router.Use(core.Header("X-Powered-By", "ALOS")) s.Router.Use(core.Header("X-Version", "1.0.0"))
Stack multiple headers:
api := s.Router.Group("/api", core.Header("X-API-Version", "v2"), core.Header("Cache-Control", "no-store"), )
Apply middleware only when a predicate function returns true. The inner middleware is skipped entirely when the predicate is false:
s.Router.Use(core.If( func(req *core.Request) bool { return req.Path != "/health" }, core.Logger(), ))
Skip auth for public routes:
s.Router.Use(core.If( func(req *core.Request) bool { return req.Header("Authorization") != "" }, AuthRequired(), ))
Compress only JSON responses:
s.Router.Use(core.If( func(req *core.Request) bool { return req.Header("Accept") == "application/json" }, core.Compress(core.CompressConfig{Level: 6, MinSize: 256}), ))
Compose a handler with specific middleware without registering them globally. Useful for per-route middleware:
s.Router.GET("/admin/stats", core.Chain( statsHandler, AuthRequired(), core.Timeout(5 * time.Second), ))
This applies AuthRequired and Timeout only to the
/admin/stats route, without affecting other routes. The middleware is applied
in the order listed (outermost first).
s.Router.POST("/upload", core.Chain( uploadHandler, core.BodyLimit(50 * 1024 * 1024), core.Timeout(120 * time.Second), )) s.Router.GET("/metrics", core.Chain( metricsHandler, core.BasicAuth(core.BasicAuthConfig{ Users: map[string]string{"ops": "secret"}, }), ))
Write your own middleware by returning a function that wraps the next handler:
func AuthRequired() core.MiddlewareFunc { return func(next core.HandlerFunc) core.HandlerFunc { return func(req *core.Request, resp *core.Response) { token := req.Header("Authorization") if token == "" { resp.Status(401).JSON([]byte(`{"error":"unauthorized"}`)) return } next(req, resp) } } } api := s.Router.Group("/api", AuthRequired())
func Timer() core.MiddlewareFunc { return func(next core.HandlerFunc) core.HandlerFunc { return func(req *core.Request, resp *core.Response) { start := time.Now() next(req, resp) elapsed := time.Since(start) resp.SetHeader("X-Response-Time", elapsed.String()) } } }