Documentation
¶
Overview ¶
Package ratelimit limits requests per key (token, user, IP). Memory uses GCRA, a smooth token bucket that needs one timestamp per key, and fits one process; Window counts fixed windows in a shared store (the database cache, so several servers on one SQL database share budgets); drivers/redis provides a shared GCRA limiter.
Middleware applies a Limiter to a route, keyed by a KeyFunc such as ByIP or ByToken; it sets the RateLimit-* headers, answers 429 with Retry-After and denies when the limiter fails. Apps build limiters with app.RateLimiters(), configured by RATE_LIMIT_STORE.
See https://github.com/AlexGalhardo/vukem/blob/main/docs/subsystems.md.
Example ¶
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"time"
"github.com/AlexGalhardo/vukem/ratelimit"
)
func main() {
limiter := ratelimit.NewMemory(2, time.Minute)
h := ratelimit.Middleware(limiter, ratelimit.ByIP)(
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusNoContent)
}),
)
for range 3 {
req := httptest.NewRequest(http.MethodGet, "/api/posts", http.NoBody)
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
fmt.Println(rec.Code, rec.Header().Get("RateLimit-Remaining"))
}
}
Output: 204 1 204 0 429 0
Index ¶
Examples ¶
Constants ¶
const DefaultIPv6Prefix = 64
DefaultIPv6Prefix is the IPv6 prefix length ByIP groups clients by when web.Services.IPv6Prefix is 0.
Variables ¶
This section is empty.
Functions ¶
func ByIP ¶
ByIP keys requests by client IP (honouring trusted proxies). IPv4 keys are per address (IPv4-mapped IPv6 counts as IPv4); IPv6 keys are per web.Services.IPv6Prefix network (default /64, RATE_LIMIT_IPV6_PREFIX in apps), because a client owning a /64 can pick a new address per request.
func ByToken ¶
ByToken keys requests by bearer token (hashed, never stored in clear), falling back to the client IP. Use it after authentication (auth.Required), behind an IP limit: before the token is verified, a client rotating made-up tokens gets a fresh bucket on every request.
func IPKey ¶ added in v0.27.0
IPKey returns the per-client part of an IP rate-limit key: an IPv4 address as is (IPv4-mapped IPv6 unmapped), an IPv6 address as its network of prefix bits ("2001:db8:1:2::/64"); a prefix outside 1-128 means DefaultIPv6Prefix. A value that is not an IP is returned as is: it is still one stable key per peer. Custom KeyFuncs that combine the client IP with other parts (email, user) use it, or ByIP, so IPv6 clients cannot rotate addresses inside their /64.
func Middleware ¶
Middleware limits requests with l, keyed by key (ByIP when nil). It sets RateLimit-Limit/Remaining/Reset on every response and answers 429 with Retry-After when the bucket is empty. A failing limiter denies the request (fail closed).
Types ¶
type Counter ¶ added in v0.23.0
type Counter interface {
Increment(ctx context.Context, key string, delta int64, ttl time.Duration) (int64, error)
}
Counter is the store a Window counts in: a cache.Store (database, Redis), a *cache.Cache (adds its key prefix) or anything whose Increment is atomic across the servers sharing it.
type Factory ¶ added in v0.23.0
Factory builds a limiter of limit requests per window; name separates the budgets of limiters sharing one store ("login", "api").
type KeyFunc ¶
KeyFunc picks the bucket for a request; "" skips limiting.
func ByUser ¶ added in v0.30.0
ByUser keys requests by the user guard authenticated, falling back to the client IP for an unauthenticated one. Use it after authentication (auth.Required) for a limit that must count per account rather than per address or per token: "10 new API keys an hour" has to survive an attacker rotating addresses, and a session request carries no bearer token, so ByToken would key every session by IP.
An anonymous caller gets its IP key, not a shared one: a constant key would let any caller exhaust everybody's budget, and an empty key makes Middleware pass the request through with no limit at all. Put an IP limit in front anyway when the route is reachable unauthenticated, the way ByToken needs one.
type Memory ¶
type Memory struct {
// contains filtered or unexported fields
}
Memory is an in-process GCRA limiter: limit requests per window, with bursts up to limit.
type Result ¶
type Result struct {
Allowed bool
Limit int
Remaining int
ResetAfter time.Duration // until the bucket is full again
RetryAfter time.Duration // when denied: until the next request is allowed
}
Result is the outcome of one Allow call.
type Window ¶ added in v0.23.0
type Window struct {
// contains filtered or unexported fields
}
Window is a fixed-window limiter over a shared Counter: every server using the same store shares one budget per key. Windows are aligned to the clock, so a client can send up to 2×limit around a window boundary (limit at the end of one, limit at the start of the next); use Memory or the Redis GCRA limiter when bursts must stay within limit.
func NewWindow ¶ added in v0.23.0
NewWindow allows limit requests per window for each key, counted in c under "ratelimit:<name>:<SHA-256 of key>:<window start>". name is 1-64 letters, digits, '.', '_' or '-'; with any other name every Allow fails, so the route denies (503) instead of sharing budgets.