pkg/gateway/server/ratelimiter.go
Source
- Package:
server - File:
pkg/gateway/server/ratelimiter.go - GitHub: https://github.com/theroutercompany/api_router/blob/main/pkg/gateway/server/ratelimiter.go
Overview
What: In-memory per-client rate limiter used by the server middleware.
Why: Provides basic protection against bursts and accidental abuse without requiring an external datastore.
How: Uses golang.org/x/time/rate per client key and periodically evicts inactive clients to bound memory usage.
Contents
Imports
import block 1
import (
"sync"
"time"
"golang.org/x/time/rate"
)
Types
type block 1
type clientLimiter struct {
limiter *rate.Limiter
lastSeen time.Time
}
clientLimiter
What: Per-client bucket holding a rate.Limiter and last-seen timestamp.
Why: Tracks both rate state and usage recency for cleanup.
How: Stored in rateLimiter.clients keyed by client identifier.
type block 2
type rateLimiter struct {
mu sync.Mutex
window time.Duration
max int
limit rate.Limit
clients map[string]*clientLimiter
nextCleanup time.Time
}
rateLimiter
What: Tracks rate limiting configuration and per-client limiters.
Why: Keeps rate limiting state isolated from request handlers and encapsulates concurrency control.
How: Uses a mutex to protect shared state, computes a per-second limit from window/max, and stores limiters in a map by client key.
Functions and Methods
newRateLimiter
What: Constructs a rateLimiter from a window duration and max requests per window.
Why: Encapsulates rate math and creates a safe "disabled" limiter when config is invalid.
How: Converts window+max into a rate.Limit, initializes the per-client map, and returns a limiter that allows all requests when window/max are non-positive.
func newRateLimiter(window time.Duration, max int) *rateLimiter {
if window <= 0 || max <= 0 {
return &rateLimiter{
window: 0,
max: 0,
}
}
seconds := window.Seconds()
if seconds <= 0 {
seconds = 1
}
limit := rate.Limit(float64(max) / seconds)
if limit <= 0 {
limit = rate.Every(window / time.Duration(max))
}
return &rateLimiter{
window: window,
max: max,
limit: limit,
clients: make(map[string]*clientLimiter),
}
}
Walkthrough
The list below documents the statements inside the function body, including nested blocks and inline closures.
- L25:
if window <= 0 || max <= 0 { return &rateLimiter{ window: 0, max: 0, } }- What: Branches conditionally.
- Why: Short-circuits early when a precondition is not met or an error/edge case is detected.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L26:
return &rateLimiter{ window: 0, max: 0, }- What: Returns from the current function.
- Why: Ends the current execution path and hands control back to the caller.
- How: Executes a
returnstatement (possibly returning values).
- L26:
- L32:
seconds := window.Seconds()- What: Defines seconds.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L33:
if seconds <= 0 { seconds = 1 }- What: Branches conditionally.
- Why: Handles different execution paths based on runtime state.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L34:
seconds = 1- What: Assigns seconds.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L34:
- L37:
limit := rate.Limit(float64(max) / seconds)- What: Defines limit.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L38:
if limit <= 0 { limit = rate.Every(window / time.Duration(max)) }- What: Branches conditionally.
- Why: Handles different execution paths based on runtime state.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L39:
limit = rate.Every(window / time.Duration(max))- What: Assigns limit.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L39:
- L42:
return &rateLimiter{ window: window, max: max, limit: limit, clients: make(map[string]*clientLimiter), }- What: Returns from the current function.
- Why: Ends the current execution path and hands control back to the caller.
- How: Executes a
returnstatement (possibly returning values).
(*rateLimiter).allow
What: Returns whether a request should be allowed for a given client key at a given time.
Why: This is the hot-path check used by the rate limit middleware.
How: Creates a per-key limiter on first sight, updates lastSeen, calls Allow(), and occasionally triggers cleanup of stale client entries.
func (r *rateLimiter) allow(key string, now time.Time) bool {
if r == nil || r.window <= 0 || r.max <= 0 {
return true
}
r.mu.Lock()
defer r.mu.Unlock()
if r.clients == nil {
r.clients = make(map[string]*clientLimiter)
}
client, ok := r.clients[key]
if !ok {
client = &clientLimiter{
limiter: rate.NewLimiter(r.limit, r.max),
lastSeen: now,
}
r.clients[key] = client
} else {
client.lastSeen = now
}
allowed := client.limiter.Allow()
if r.nextCleanup.IsZero() || now.After(r.nextCleanup) {
r.cleanupLocked(now)
r.nextCleanup = now.Add(r.window)
}
return allowed
}
Walkthrough
The list below documents the statements inside the function body, including nested blocks and inline closures.
- L51:
if r == nil || r.window <= 0 || r.max <= 0 { return true }- What: Branches conditionally.
- Why: Short-circuits early when a precondition is not met or an error/edge case is detected.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L52:
return true- What: Returns from the current function.
- Why: Ends the current execution path and hands control back to the caller.
- How: Executes a
returnstatement (possibly returning values).
- L52:
- L55:
r.mu.Lock()- What: Calls r.mu.Lock.
- Why: Performs side effects or delegates work to a helper.
- How: Executes the expression statement.
- L56:
defer r.mu.Unlock()- What: Defers a call for cleanup.
- Why: Ensures the deferred action runs even on early returns.
- How: Schedules the call to run when the surrounding function returns.
- L58:
if r.clients == nil { r.clients = make(map[string]*clientLimiter) }- What: Branches conditionally.
- Why: Handles different execution paths based on runtime state.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L59:
r.clients = make(map[string]*clientLimiter)- What: Assigns r.clients.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L59:
- L62:
client, ok := r.clients[key]- What: Defines client, ok.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L63:
if !ok { client = &clientLimiter{ limiter: rate.NewLimiter(r.limit, r.max), lastSeen: now, } r.clients[key] = client } else { client.lastSe…- What: Branches conditionally.
- Why: Handles different execution paths based on runtime state.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L64:
client = &clientLimiter{ limiter: rate.NewLimiter(r.limit, r.max), lastSeen: now, }- What: Assigns client.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L68:
r.clients[key] = client- What: Assigns r.clients[key].
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L70:
client.lastSeen = now- What: Assigns client.lastSeen.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L64:
- L73:
allowed := client.limiter.Allow()- What: Defines allowed.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L75:
if r.nextCleanup.IsZero() || now.After(r.nextCleanup) { r.cleanupLocked(now) r.nextCleanup = now.Add(r.window) }- What: Branches conditionally.
- Why: Handles different execution paths based on runtime state.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L76:
r.cleanupLocked(now)- What: Calls r.cleanupLocked.
- Why: Performs side effects or delegates work to a helper.
- How: Executes the expression statement.
- L77:
r.nextCleanup = now.Add(r.window)- What: Assigns r.nextCleanup.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L76:
- L80:
return allowed- What: Returns from the current function.
- Why: Ends the current execution path and hands control back to the caller.
- How: Executes a
returnstatement (possibly returning values).
(*rateLimiter).cleanupLocked
What: Removes client limiter entries that have not been seen recently.
Why: Prevents unbounded growth of the clients map in long-running processes.
How: Deletes clients whose lastSeen is older than two windows, assuming they're no longer active.
func (r *rateLimiter) cleanupLocked(now time.Time) {
if r.clients == nil {
return
}
threshold := now.Add(-2 * r.window)
for key, client := range r.clients {
if client.lastSeen.Before(threshold) {
delete(r.clients, key)
}
}
}
Walkthrough
The list below documents the statements inside the function body, including nested blocks and inline closures.
- L84:
if r.clients == nil { return }- What: Branches conditionally.
- Why: Short-circuits early when a precondition is not met or an error/edge case is detected.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L85:
return- What: Returns from the current function.
- Why: Ends the current execution path and hands control back to the caller.
- How: Executes a
returnstatement (possibly returning values).
- L85:
- L88:
threshold := now.Add(-2 * r.window)- What: Defines threshold.
- Why: Keeps intermediate state available for later steps in the function.
- How: Evaluates the right-hand side expressions and stores results in the left-hand variables.
- L89:
for key, client := range r.clients { if client.lastSeen.Before(threshold) { delete(r.clients, key) } }- What: Iterates over a collection.
- Why: Processes multiple elements with the same logic.
- How: Executes a
for ... rangeloop. - Nested steps:
- L90:
if client.lastSeen.Before(threshold) { delete(r.clients, key) }- What: Branches conditionally.
- Why: Handles different execution paths based on runtime state.
- How: Evaluates the condition and executes the matching branch.
- Nested steps:
- L91:
delete(r.clients, key)- What: Calls delete.
- Why: Performs side effects or delegates work to a helper.
- How: Executes the expression statement.
- L91:
- L90: