Skip to content
wiki edited this page Sep 4, 2026 · 1 revision

rextension-ratelimit

Sliding-window rate limiting for Rex, at three levels: per endpoint → per router → global.

go get github.com/kryovyx/rextension-ratelimit
import (
	"github.com/kryovyx/rex"
	ratelimit "github.com/kryovyx/rextension-ratelimit"
)

app := rex.New(ratelimit.WithRateLimit(
	ratelimit.WithGlobalLimit(600, time.Minute),
	ratelimit.WithTrustedProxies(ratelimit.PrivateNetworks()...),
))

A route can override that:

type LoginRoute struct{ rxroute.Route }

func (r *LoginRoute) RateLimit() ratelimit.LimitConfig {
	return ratelimit.LimitConfig{Rate: 5, Window: time.Minute}
}

Every response carries the headers

Not only refusals:

X-RateLimit-Limit: 600
X-RateLimit-Remaining: 583
X-RateLimit-Reset: 1757000000

A client that can read its remaining quota can slow down. A client that cannot discovers the limit by exceeding it.

For a browser client, those headers must be exposed — which rextension-cors does by default.

When the limit is exceeded

HTTP/1.1 429 Too Many Requests
Retry-After: 34
Content-Type: application/problem+json

{"type":"urn:rex:problem:rate-limit-exceeded","title":"Too Many Requests",
 "status":429,"detail":"rate limit exceeded"}

Where it runs

rextension.PriorityRateLimit (300) — before authentication (400).

That order is not arbitrary. Rate limiting after authentication means every flooded request costs a password hash or a token verification before being rejected: the limiter then protects nothing, it just adds work.

Two things that make it actually work

Per-endpoint limits are resolved at build time, from the route itself. Resolution used to happen per request from the live URL against an index keyed by the route's pattern — those never match for a parameterized route, so a per-endpoint limit silently never applied to any route with a path parameter. A route declaring 5/minute was limited at whatever the global rate happened to be, and nothing said so.

X-Forwarded-For is walked right to left, through trusted hops only. The header is client-appendable, so the leftmost entry is the attacker's own value. Taking it — the obvious reading, and the one most naive implementations use — lets a client rotate the header and defeat the limiter entirely. See Behind a Proxy.

Pages

Clone this wiki locally