Documentation
¶
Overview ¶
Package ratelimit provides a per-key token-bucket rate limiter shared across the platform's HTTP surfaces (the public portal viewer and the OAuth authorization server). Keeping one implementation avoids per-caller forks of the bucket math, cleanup goroutine, and eviction policy.
The limiter is keyed on an arbitrary string. Callers choose the key: a client IP for per-client fairness, or a fixed sentinel for a global backstop that bounds total throughput regardless of client attribution. Client-IP extraction (including trusted-proxy handling) lives in Resolver (clientip.go); this file is only the bucket accounting.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Config ¶
type Config struct {
RequestsPerMinute int `yaml:"requests_per_minute"`
BurstSize int `yaml:"burst_size"`
}
Config configures a token-bucket limiter.
type HTTPLimiter ¶
type HTTPLimiter struct {
// contains filtered or unexported fields
}
HTTPLimiter rate-limits requests to a single HTTP endpoint with two layers: a per-client-IP bucket for fairness and a global bucket that bounds total throughput regardless of how requests attribute to IPs. Client-IP attribution (including trusted-proxy handling) is delegated to a Resolver, so the per-IP layer is not trivially defeated by a spoofed X-Forwarded-For.
func NewHTTPLimiter ¶
func NewHTTPLimiter(rpm, burst int, resolver *Resolver) *HTTPLimiter
NewHTTPLimiter builds an HTTPLimiter for one endpoint from a per-IP rate/burst and a client-IP resolver. The global backstop bucket is sized at globalBackstopFactor times the per-IP rate and burst. Call Close to stop the background cleanup goroutines.
func (*HTTPLimiter) Allow ¶
func (h *HTTPLimiter) Allow(r *http.Request) bool
Allow reports whether the request may proceed. A request is admitted only when it is within BOTH the client's own per-IP budget and the global budget, and a token is consumed from each bucket only on admission:
- Peek per-IP (no consume). A client over its own limit is rejected here without ever touching the global bucket, so an over-limit client cannot drain the shared backstop against everyone else.
- Consume global. If the system is saturated the request is rejected here with the per-IP bucket left untouched, so a legitimate low-volume client is not additionally penalized by a global-saturation event.
- Consume per-IP to record the admitted request.
func (*HTTPLimiter) Close ¶
func (h *HTTPLimiter) Close()
Close stops both buckets' background cleanup goroutines.
func (*HTTPLimiter) RetryAfter ¶
func (h *HTTPLimiter) RetryAfter() int
RetryAfter returns the advisory Retry-After value in seconds: the time for one per-IP token to refill, at least 1.
type Limiter ¶
type Limiter struct {
// contains filtered or unexported fields
}
Limiter provides per-key token-bucket rate limiting.
func New ¶
New creates a rate limiter from config, applying defaults for non-positive values, and starts the background eviction goroutine. Call Close to stop it.
func (*Limiter) Allow ¶
Allow checks whether a request under the given key should be allowed, consuming one token when it is.
type Resolver ¶
type Resolver struct {
// contains filtered or unexported fields
}
Resolver extracts the originating client IP from an HTTP request with trusted-proxy awareness. It exists because naively trusting the leftmost X-Forwarded-For entry is both spoofable (a caller sets any leftmost value to mint unlimited rate-limit buckets) and fragile behind a load balancer that does not forward the header (every client collapses onto the proxy IP). Neither failure mode is acceptable for a rate limiter guarding an unauthenticated, CPU-amplifying endpoint.
Trust model: X-Forwarded-For is consulted ONLY when the direct peer (RemoteAddr) is itself a configured trusted proxy. In that case the client IP is the rightmost XFF entry that is not itself a trusted proxy — the last hop a trusted proxy actually observed, which a spoofing client cannot forge past the trust boundary. When no trusted proxies are configured, or the peer is not trusted, the header is ignored entirely and RemoteAddr is used. This makes the safe default (trust nothing) the zero value.
func NewResolver ¶
NewResolver builds a Resolver from a list of trusted proxy CIDRs (e.g. "10.0.0.0/8", "127.0.0.1/32"). An empty list yields a resolver that always returns the direct peer address and never consults X-Forwarded-For. A malformed CIDR is a configuration error and is returned.