ratelimit

package module
v0.3.0 Latest Latest
Warning

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

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

README

RateLimit

Go Reference Go Version License

Limit how many requests each client can make. Uses a token bucket so you get a steady rate plus short bursts. Good for protecting your API from abuse and keeping usage fair.

Full docs: Middleware Guide and Middleware Reference.

Features

  • Token bucket algorithm (smooth rate with configurable burst)
  • Limit per client IP by default, or per user / custom key
  • Skip specific paths (e.g. health checks)
  • Standard rate limit headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset
  • Custom handler when the limit is exceeded
  • Safe for concurrent use

Installation

go get rivaas.dev/middleware/ratelimit

Requires Go 1.25 or later.

Quick Start

package main

import (
    "net/http"
    "rivaas.dev/router"
    "rivaas.dev/middleware/ratelimit"
)

func main() {
    r := router.New()
    r.Use(ratelimit.New(
        ratelimit.WithRequestsPerSecond(100),
        ratelimit.WithBurst(20),
    ))

    r.GET("/api/data", func(c *router.Context) {
        c.JSON(http.StatusOK, map[string]string{"data": "value"})
    })

    http.ListenAndServe(":8080", r)
}

Configuration

Option What it does
WithRequestsPerSecond Average rate (tokens per second)
WithBurst Max burst size (default: same as rate)
WithKeyFunc How to identify the client (default: by IP)
WithSkipPaths Paths that are not rate limited
WithOnLimitExceeded Custom response when limit is hit
WithLogger Logger for rate limit events

Limit per user instead of per IP:

r.Use(ratelimit.New(
    ratelimit.WithRequestsPerSecond(50),
    ratelimit.WithBurst(10),
    ratelimit.WithKeyFunc(func(c *router.Context) string {
        return basicauth.Username(c) // or from JWT, session, etc.
    }),
))

Response headers

When a request is allowed, the middleware adds:

  • X-RateLimit-Limit – Max requests in the window
  • X-RateLimit-Remaining – Requests left in the current window
  • X-RateLimit-Reset – When the window resets (Unix timestamp)

Examples

A runnable example is in the example/ directory:

cd example
go run main.go

Then send many requests and watch the rate limit kick in.

Learn More

License

Apache License 2.0 – see LICENSE for details.

Documentation

Overview

Package ratelimit provides middleware for token bucket rate limiting per client.

This middleware implements rate limiting using a token bucket algorithm to control the rate of requests from clients. It supports per-IP, per-user, or custom key-based rate limiting with configurable rate and burst limits.

Basic Usage

import "rivaas.dev/middleware/ratelimit"

r := router.MustNew()
r.Use(ratelimit.New(
    ratelimit.WithRequestsPerSecond(100),
    ratelimit.WithBurst(20),
))

Rate Limiting Strategies

The middleware supports different rate limiting strategies:

  • Per IP: Limit requests per client IP address (default)
  • Per User: Limit requests per authenticated user
  • Custom Key: Use a custom KeyFunc to determine the rate limit key

Configuration Options

  • RequestsPerSecond: Average rate of requests allowed (required)
  • Burst: Maximum burst size (default: same as RequestsPerSecond)
  • KeyFunc: Function to determine rate limit key (default: per IP)
  • SkipPaths: Paths to exclude from rate limiting (e.g., /health)
  • Logger: Optional logger for rate limit events
  • OnLimitExceeded: Custom handler when rate limit is exceeded

Custom Key Function

import "rivaas.dev/middleware/ratelimit"

r.Use(ratelimit.New(
    ratelimit.WithRequestsPerSecond(100),
    ratelimit.WithKeyFunc(func(c *router.Context) string {
        // Rate limit per user ID
        return c.Param("user_id")
    }),
))

Rate Limit Headers

The middleware sets standard rate limit headers in responses:

  • X-RateLimit-Limit: Maximum requests allowed per window
  • X-RateLimit-Remaining: Remaining requests in current window
  • X-RateLimit-Reset: Unix timestamp when the rate limit resets

The token bucket algorithm supports concurrent access.

Package ratelimit provides middleware for rate limiting HTTP requests using configurable stores (in-memory, Redis, etc.) and token bucket algorithm.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func New

func New(opts ...Option) router.HandlerFunc

New creates a token bucket rate limiter middleware using functional options. Defaults: 100 requests/second, burst of 20, rate limit by IP.

Example:

r.Use(ratelimit.New(
    ratelimit.WithRequestsPerSecond(50),
    ratelimit.WithBurst(10),
))

func PerRoute

PerRoute wraps a rate limiter middleware for per-route application. This allows different rate limits for different routes.

Example:

r.GET("/expensive", handler, ratelimit.PerRoute(
    ratelimit.WithSlidingWindow(...),
))

func WithSlidingWindow

func WithSlidingWindow(sw SlidingWindow, opts CommonOptions) router.HandlerFunc

WithSlidingWindow creates a sliding window rate limiter middleware.

func WithTokenBucket

func WithTokenBucket(tb TokenBucket, opts CommonOptions) router.HandlerFunc

WithTokenBucket creates a token bucket rate limiter middleware.

Types

type CommonOptions

type CommonOptions struct {
	Key        KeyFunc                     // Function to derive rate limit key
	Headers    bool                        // Emit RateLimit-* headers (IETF draft)
	Enforce    bool                        // true = block on exceed (429), false = report-only
	OnExceeded func(*router.Context, Meta) // Callback when limit exceeded
	// contains filtered or unexported fields
}

CommonOptions contains shared configuration for all rate limiters.

type InMemoryStore

type InMemoryStore struct {
	// contains filtered or unexported fields
}

InMemoryStore implements in-memory sliding window storage.

func NewInMemoryStore

func NewInMemoryStore() *InMemoryStore

NewInMemoryStore creates a new in-memory sliding window store.

func (*InMemoryStore) GetCounts

func (s *InMemoryStore) GetCounts(_ context.Context, key string, window time.Duration) (int, int, int64, error)

GetCounts returns current count, previous count, and window start time.

func (*InMemoryStore) Incr

func (s *InMemoryStore) Incr(_ context.Context, key string, window time.Duration) error

Incr increments the current window count.

type InMemoryTokenBucketStore

type InMemoryTokenBucketStore struct {
	// contains filtered or unexported fields
}

InMemoryTokenBucketStore implements in-memory token bucket storage. This is the default store implementation used by the token bucket rate limiter.

func NewInMemoryTokenBucketStore

func NewInMemoryTokenBucketStore(rate, burst int) *InMemoryTokenBucketStore

NewInMemoryTokenBucketStore creates a new in-memory token bucket store. This is exposed to allow custom configuration of the default store.

Example:

store := ratelimit.NewInMemoryTokenBucketStore(100, 20)
r.Use(ratelimit.WithTokenBucket(
    ratelimit.TokenBucket{Rate: 100, Burst: 20, Store: store},
    ratelimit.CommonOptions{},
))

func (*InMemoryTokenBucketStore) Allow

func (s *InMemoryTokenBucketStore) Allow(key string, now time.Time) (allowed bool, remaining, resetSeconds int)

Allow checks if a request is allowed and returns remaining tokens and reset time. This implements the TokenBucketStore interface.

type KeyFunc

type KeyFunc func(*router.Context) string

KeyFunc determines the rate limit key for a request (e.g., per IP, per user, per route).

type Meta

type Meta struct {
	Limit        int           // Rate limit (requests per window)
	Remaining    int           // Remaining requests in current window
	ResetSeconds int           // Seconds until window reset
	Window       time.Duration // Window duration
	Key          string        // Rate limit key (e.g., "ip:192.168.1.1")
	Route        string        // Matched route pattern
	Method       string        // HTTP method
	ClientIP     string        // Client IP address
}

Meta contains rate limit metadata for callbacks and logging.

type Option

type Option func(*config)

Option defines functional options for rate limit middleware configuration.

func WithBurst

func WithBurst(burst int) Option

WithBurst sets the maximum burst size. Burst allows clients to make multiple requests instantly up to this limit. Default: 20 requests

Example:

ratelimit.New(ratelimit.WithBurst(10))

func WithCleanupInterval

func WithCleanupInterval(interval time.Duration) Option

WithCleanupInterval sets how often to clean up expired limiters. Default: 1 minute

func WithHandler

func WithHandler(fn func(*router.Context)) Option

WithHandler sets a custom handler for when rate limit is exceeded. Default: Returns 429 Too Many Requests with JSON error

Example:

ratelimit.New(
    ratelimit.WithHandler(func(c *router.Context) {
        c.String(http.StatusTooManyRequests, "Slow down! Try again in a minute.")
    }),
)

func WithKeyFunc

func WithKeyFunc(fn func(*router.Context) string) Option

WithKeyFunc sets a custom function to extract the rate limit key from requests. Common use cases:

  • Per-IP limiting: Use client IP (default)
  • Per-user limiting: Use user ID from authentication
  • Per-API key limiting: Use API key from header

Example:

ratelimit.New(
    ratelimit.WithKeyFunc(func(c *router.Context) string {
        // Rate limit by user ID from auth token
        return c.Request.Header.Get("X-User-ID")
    }),
)

func WithLimiterTTL

func WithLimiterTTL(ttl time.Duration) Option

WithLimiterTTL sets how long to keep inactive limiters before cleanup. Default: 5 minutes

func WithLogger

func WithLogger(logger *slog.Logger) Option

WithLogger sets the slog.Logger for error logging. If not provided, errors will be silently ignored.

Uses the standard library's log/slog package for structured logging:

import "log/slog"

logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
ratelimit.New(ratelimit.WithLogger(logger))

Example:

import "log/slog"

logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
r.Use(ratelimit.New(ratelimit.WithLogger(logger)))

func WithRequestsPerSecond

func WithRequestsPerSecond(rps int) Option

WithRequestsPerSecond sets the number of requests allowed per second. Default: 100 requests/second

Example:

ratelimit.New(ratelimit.WithRequestsPerSecond(50))

type SlidingWindow

type SlidingWindow struct {
	Window time.Duration // Fixed window duration (e.g., 1 minute)
	Limit  int           // Requests per window
	Store  WindowStore   // Storage backend (in-memory, Redis, etc.)
}

SlidingWindow implements sliding window rate limiting. Uses two fixed windows (current + previous) for accurate counting.

type TokenBucket

type TokenBucket struct {
	Rate  int              // Tokens per second
	Burst int              // Maximum tokens (burst capacity)
	Store TokenBucketStore // Optional custom store (defaults to in-memory)
}

TokenBucket implements token bucket rate limiting. Allows bursts up to Burst size, refills at Rate tokens per second.

type TokenBucketStore

type TokenBucketStore interface {
	// Allow checks if a request is allowed for the given key.
	// Returns (allowed, remaining tokens, reset time in seconds).
	Allow(key string, now time.Time) (allowed bool, remaining, resetSeconds int)
}

TokenBucketStore provides storage for token bucket rate limiting. This allows custom implementations (e.g., Redis-backed) for distributed systems.

type WindowStore

type WindowStore interface {
	// GetCounts returns (current count, previous count, window start unix time, error).
	GetCounts(ctx context.Context, key string, window time.Duration) (int, int, int64, error)
	// Incr increments the current window count.
	Incr(ctx context.Context, key string, window time.Duration) error
}

WindowStore provides storage for sliding window rate limiting.

Jump to

Keyboard shortcuts

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