Edge Middleware
The edge package provides request-preprocessing middleware. Despite the name it runs inside your Nimbus app (not on a CDN): it sits in front of your routes and can short-circuit, redirect, rewrite, or decorate requests before they reach your handlers — geo routing, A/B tests, maintenance windows, security headers, basic auth, CORS, rate limiting, and response caching. It reads CDN geo headers (CF-IPCountry, X-Vercel-IP-Country, …) when a real CDN sits in front of you.
Setup
import "github.com/CodeSyncr/nimbus/edge"
rt := edge.New(edge.Config{MaxExecTime: 50 * time.Millisecond})
rt.Handle("/geo", func(req *edge.Request) *edge.Response {
if req.Geo.Country == "DE" {
return edge.Redirect("/de"+req.Path, 302)
}
return edge.Next() // continue to the normal handler
})
app.Use(rt.Plugin()) // applies the middleware + mounts /_edge/metrics
// or, without the plugin:
// app.Router.Use(rt.Middleware())
The request & response
Handlers receive an *edge.Request (req.Method, req.Path, req.Header("X"), req.QueryParam("k"), req.IP, req.Geo, req.Body, req.ParseJSON(&v)) and return an *edge.Response. The body read here is restored, so downstream handlers still receive it.
| Constructor | Effect |
|---|---|
edge.Next() | Continue to the normal handler (optionally with added headers) |
edge.JSON(status, v) | Short-circuit with a JSON response |
edge.HTML(status, html) / edge.Respond(status, text) | HTML / plain-text response |
edge.Redirect(url, status) | Client-visible redirect |
edge.Rewrite(path) | Change the path and pass through (no redirect) |
Route options
// Restrict to methods:
rt.Handle("/api/*", guard).Methods("POST", "PUT")
// Cache the response (key defaults to "method:path"):
rt.Handle("/pricing", pricing).WithCache(5 * time.Minute)
// Custom cache key:
rt.Handle("/geo-page", page).WithCache(time.Minute, func(r *edge.Request) string {
return r.Geo.Country + ":" + r.Path
})
Paths support a trailing * wildcard. On handler panic or timeout, Config.Fallback decides behavior: FallbackNext (pass through, default), FallbackError (502), or FallbackCached (serve the route's last successful response).
Built-in patterns
Ready-made handlers in the edge package:
rt.Handle("/*", edge.SecurityHeaders())
rt.Handle("/*", edge.CORSHeaders([]string{"*"}, []string{"GET","POST"}, []string{"Content-Type"}))
rt.Handle("/api/*", edge.RateLimit(100, time.Minute))
rt.Handle("/admin/*", edge.BasicAuth("admin", map[string]string{"user": "pass"}))
rt.Handle("/*", edge.Maintenance("<h1>Down for maintenance</h1>", "203.0.113.7"))
rt.Handle("/", edge.GeoRouter(map[string]string{"DE": "/de", "FR": "/fr"}, "/en"))
rt.Handle("/", edge.ABTest([]edge.ABVariant{{Name: "A", Path: "/a", Weight: 1}, {Name: "B", Path: "/b", Weight: 1}}))
Notes
MaxExecTimebounds how long the runtime waits and passes a timeout-scoped context viareq.Context(). Go cannot forcibly stop a goroutine, so a handler that ignores its context keeps running after a timeout — keep handlers fast and context-aware.- Runtime counters are exposed at
GET /_edge/metrics(invocations, errors, cache hits, timeouts, average latency).