ratelimit

package
v1.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 19, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

Documentation

Overview

Package ratelimit provides token-bucket rate limiting middleware for celeris.

The limiter uses a sharded token-bucket algorithm keyed by client IP (by default). Each key gets an independent bucket that refills at the configured rate up to the burst capacity. A background goroutine periodically evicts expired buckets.

Basic usage with defaults (10 RPS, burst 20):

server.Use(ratelimit.New())

Human-readable rate string (100 per minute, burst 200):

server.Use(ratelimit.New(ratelimit.Config{
    Rate:  "100-M",
    Burst: 200,
}))

Custom limits keyed by API key:

server.Use(ratelimit.New(ratelimit.Config{
    RPS:   100,
    Burst: 200,
    KeyFunc: func(c *celeris.Context) string {
        return c.Header("x-api-key")
    },
}))

The middleware sets X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset headers on every allowed response, and Retry-After on denied responses (429 Too Many Requests). Set DisableHeaders to suppress all rate limit headers.

Config.SlidingWindow enables a sliding window counter algorithm for smoother rate limiting near window boundaries.

Config.RateFunc allows per-request rate selection, with distinct limiters cached up to Config.MaxDynamicLimiters (default 1024).

ParseRate parses rate strings in "<count>-<unit>" format (S, M, H, D) into RPS and burst values, returning an error for malformed input.

ErrTooManyRequests is the exported sentinel error (429) returned on deny, usable with errors.Is for error handling in upstream middleware.

Set Config.Skip to bypass the middleware dynamically, or Config.SkipPaths for exact-match path exclusions.

Config.Store allows plugging in an external storage backend (e.g., Redis) that implements the Store interface. The store handles its own rate logic.

Config.SkipFailedRequests and Config.SkipSuccessfulRequests refund tokens for requests that fail (status >= 400) or succeed (status < 400), respectively. Store backends must implement StoreUndo for refunds.

Config.CleanupContext controls the lifetime of background goroutines. When the context is cancelled, cleanup goroutines stop. If nil, they run until the process exits.

Note: New spawns one or more background goroutines for bucket eviction. Set CleanupContext to a cancellable context to ensure they are stopped when the middleware is no longer needed.

Reverse Proxy Integration

The default KeyFunc uses c.ClientIP(). When behind a reverse proxy, install the proxy middleware via Server.Pre() so rate limits apply to real client IPs, not the proxy's IP:

server.Pre(proxy.New(proxy.Config{
    TrustedProxies: []string{"10.0.0.0/8"},
}))
server.Use(ratelimit.New()) // rate-limits by real client IP

Without proxy middleware, all clients behind the same proxy share a single rate-limit bucket.

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrDynamicLimitersExhausted = errors.New("ratelimit: dynamic limiter map full")

ErrDynamicLimitersExhausted is returned when the dynamic limiter map has reached MaxDynamicLimiters and a new rate string is requested.

View Source
var ErrTooManyRequests = celeris.NewHTTPError(429, "Too Many Requests")

ErrTooManyRequests is returned when the rate limit is exceeded.

Functions

func New

func New(config ...Config) celeris.HandlerFunc

New creates a rate limit middleware with the given config.

Example
package main

import (
	"github.com/goceleris/celeris/middleware/ratelimit"
)

func main() {
	// Zero-config: 10 RPS, burst 20, keyed by client IP.
	_ = ratelimit.New()
}
Example (ApiKey)
package main

import (
	"github.com/goceleris/celeris"

	"github.com/goceleris/celeris/middleware/ratelimit"
)

func main() {
	// Rate limit by API key header.
	_ = ratelimit.New(ratelimit.Config{
		Rate: "1000-H",
		KeyFunc: func(c *celeris.Context) string {
			return c.Header("x-api-key")
		},
	})
}
Example (HumanReadable)
package main

import (
	"github.com/goceleris/celeris/middleware/ratelimit"
)

func main() {
	// Human-readable rate: 100 requests per minute.
	_ = ratelimit.New(ratelimit.Config{
		Rate: "100-M",
	})
}

func ParseRate

func ParseRate(s string) (rps float64, burst int, err error)

ParseRate parses a human-readable rate string into RPS and burst values. Format: "<count>-<unit>" where unit is S (second), M (minute), H (hour), D (day). Examples: "100-M" (100 per minute), "1000-H" (1000 per hour), "5-S" (5 per second). The burst is set equal to the count.

Example
package main

import (
	"github.com/goceleris/celeris/middleware/ratelimit"
)

func main() {
	rps, burst, _ := ratelimit.ParseRate("100-M")
	_ = rps   // 1.6667 (100/60)
	_ = burst // 100
}

func ValidateConfig

func ValidateConfig(cfg Config) error

ValidateConfig checks the configuration for errors without panicking. Returns nil if the configuration is valid. Users loading config from files or untrusted sources can call this before New() to check their config safely.

Types

type Config

type Config struct {
	// Skip defines a function to skip this middleware for certain requests.
	Skip func(c *celeris.Context) bool

	// Store, when set, replaces the built-in sharded limiter with an
	// external storage backend (e.g., Redis). The store is responsible
	// for its own rate and burst logic; RPS, Burst, Shards, and
	// CleanupInterval are ignored.
	Store Store

	// RPS is the refill rate in requests per second per key. Default: 10.
	RPS float64

	// Burst is the maximum tokens (burst capacity). Default: 20.
	Burst int

	// Rate is a human-readable rate string (e.g., "100-M" for 100 per minute).
	// Supported units: S (second), M (minute), H (hour), D (day).
	// Takes precedence over RPS/Burst when set. If Burst is also set
	// explicitly, the user's Burst is preserved (Rate only overrides RPS).
	Rate string

	// RateFunc, when set, is called per-request to determine the rate.
	// Returns a rate string in the same format as Rate (e.g., "100-M").
	// Falls back to the static Rate/RPS/Burst when it returns an empty string.
	// Parsed via ParseRate.
	RateFunc func(c *celeris.Context) (rate string, err error)

	// KeyFunc extracts the rate limit key from the request. Default:
	// c.ClientIP() — which means the proxy middleware MUST be installed
	// (Server.Pre(proxy.New(...))) when running behind a reverse proxy.
	// Without proxy, ClientIP() returns the immediate peer (the load
	// balancer's address), so all real clients share one bucket and a
	// single noisy client triggers a global DoS for everyone behind the
	// same hop. With a misconfigured TrustedProxies range, attackers can
	// spoof X-Forwarded-For and escape their bucket. Verify the chain
	// before relying on the default.
	KeyFunc func(c *celeris.Context) string

	// SkipPaths lists paths to skip (exact match).
	SkipPaths []string

	// Shards is the number of lock shards. Default: runtime.NumCPU().
	// Rounded up to the next power of 2.
	Shards int

	// CleanupInterval is how often expired buckets are cleaned. Default: 1 minute.
	CleanupInterval time.Duration

	// CleanupContext, if set, controls cleanup goroutine lifetime.
	// When the context is cancelled, the cleanup goroutine stops.
	// If nil, cleanup runs until the process exits.
	CleanupContext context.Context

	// DisableHeaders disables X-RateLimit-* response headers.
	DisableHeaders bool

	// SlidingWindow, when true, uses a sliding window counter instead
	// of the default token bucket algorithm. The sliding window tracks
	// the previous and current window counts, weighted by the elapsed
	// fraction of the current window. This provides smoother rate limiting
	// near window boundaries.
	SlidingWindow bool

	// SkipFailedRequests, when true, refunds the token for requests
	// whose downstream handler returns a status >= 400. The token is
	// refunded after c.Next() completes.
	SkipFailedRequests bool

	// SkipSuccessfulRequests, when true, refunds the token for requests
	// whose downstream handler returns a status < 400. The token is
	// refunded after c.Next() completes.
	SkipSuccessfulRequests bool

	// LimitReached is called when a request is rate-limited.
	// If nil, returns 429 Too Many Requests.
	//
	// Deprecated: use [ErrorHandler] for naming consistency with the
	// rest of the middleware family (jwt, keyauth, basicauth, csrf,
	// recovery). LimitReached is kept working for backwards
	// compatibility; if both fields are set, ErrorHandler wins.
	LimitReached func(c *celeris.Context) error

	// ErrorHandler is called when a request is rate-limited. The err
	// argument is the sentinel [ErrTooManyRequests] so callers can
	// distinguish the cause from other middleware errors via
	// errors.Is. If nil, falls back to LimitReached, then to a
	// 429 Too Many Requests default.
	ErrorHandler func(c *celeris.Context, err error) error

	// MaxDynamicLimiters caps the number of distinct rate strings cached
	// when RateFunc is used. When exceeded, new rate strings are rejected
	// with an error. Default: 1024.
	MaxDynamicLimiters int
}

Config defines the rate limit middleware configuration.

type Store

type Store interface {
	// Allow checks if the request identified by key is allowed.
	// Returns whether the request is allowed, the remaining token count,
	// and the time at which the bucket resets.
	Allow(key string) (allowed bool, remaining int, resetAt time.Time, err error)
}

Store defines the interface for pluggable rate limit storage backends (e.g., Redis). The store handles its own rate/burst logic; Config.RPS and Config.Burst are ignored when Store is set.

type StoreUndo

type StoreUndo interface {
	Undo(key string) error
}

StoreUndo is an optional interface that Store implementations may satisfy to support token refunds. Used by SkipFailedRequests and SkipSuccessfulRequests.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL