ratelimit

package
v1.0.2 Latest Latest
Warning

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

Go to latest
Published: Dec 31, 2025 License: Apache-2.0 Imports: 11 Imported by: 3

README

ratelimiter

Rate limiter for any resource type (not just http requests), inspired by Cloudflare's approach: How we built rate limiting capable of scaling to millions of domains.

Usage

package main

import (
	"fmt"
	"log"
	"time"

	"github.com/theopenlane/core/pkg/middleware/ratelimiter"
)

func main() {
	limitedKey := "key"
	windowSize := 1 * time.Minute
	// create map data store for rate limiter and set each element's expiration time to 2*windowSize and old data flush interval to 10*time.Second
	dataStore := ratelimiter.NewMapLimitStore(2*windowSize, 10*time.Second)

	var maxLimit int64 = 5
	// allow 5 requests per windowSize (1 minute)
	rateLimiter := ratelimiter.New(dataStore, maxLimit, windowSize)

	for i := 0; i < 10; i++ {
		limitStatus, err := rateLimiter.Check(limitedKey)
		if err != nil {
			log.Fatal(err)
		}

		if limitStatus.IsLimited {
			fmt.Printf("too high rate for key: %s: rate: %f, limit: %d\nsleep: %s", limitedKey, limitStatus.CurrentRate, maxLimit, *limitStatus.LimitDuration)
			time.Sleep(*limitStatus.LimitDuration)
		} else {
			err := rateLimiter.Inc(limitedKey)
			if err != nil {
				log.Fatal(err)
			}
		}
	}
}
Rate-limit IP requests in http middleware
func rateLimitMiddleware(rateLimiter *ratelimiter.RateLimiter) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			remoteIP := GetRemoteIP([]string{"X-Forwarded-For", "RemoteAddr", "True-Client-IP"}, 0, r)
			key := fmt.Sprintf("%s_%s_%s", remoteIP, r.URL.String(), r.Method)

			limitStatus, err := rateLimiter.Check(key)
			if err != nil {
				// if rate limit error then pass the request
				next.ServeHTTP(w, r)
			}

			if limitStatus.IsLimited {
				w.WriteHeader(http.StatusTooManyRequests)
				return
			}

			rateLimiter.Inc(key)
			next.ServeHTTP(w, r)
		})
	}
}

func hello(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
}

func main() {
	windowSize := 1 * time.Minute
	// create map data store for rate limiter and set each element's expiration time to 2*windowSize and old data flush interval to 10*time.Second
	dataStore := ratelimiter.NewMapLimitStore(2*windowSize, 10*time.Second)
	// allow 5 requests per windowSize (1 minute)
	rateLimiter := ratelimiter.New(dataStore, 5, windowSize)

	rateLimiterHandler := rateLimitMiddleware(rateLimiter)
	helloHandler := http.HandlerFunc(hello)
	http.Handle("/", rateLimiterHandler(helloHandler))

	log.Fatal(http.ListenAndServe(":8080", nil))

}

See full example

Implement your own limit data store (or external persistence method)

To use custom data store (memcached, Redis, MySQL etc.) you just need to implement the LimitStore interface, for example:

type FakeDataStore struct{}

func (f FakeDataStore) Inc(key string, window time.Time) error {
	return nil
}

func (f FakeDataStore) Get(key string, previousWindow, currentWindow time.Time) (prevValue int64, currValue int64, err error) {
	return 0, 0, nil
}

rateLimiter := ratelimiter.New(FakeDataStore{}, maxLimit, windowSize)

Dry-run mode

When the middleware is configured with dryRun: true, limit checks are still executed and logged using zerolog, but requests are not blocked. This is useful for validating rate limit settings in production before enforcing them.

Documentation

Overview

Package ratelimit implements a rate limiting middleware

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func RateLimiterWithConfig

func RateLimiterWithConfig(conf *Config) echo.MiddlewareFunc

RateLimiterWithConfig returns a middleware function for rate limiting requests with a supplied config.

Types

type Config

type Config struct {
	Enabled bool `json:"enabled" koanf:"enabled" default:"false"`
	// Options enables configuring multiple concurrent rate windows that must all pass.
	Options []RateOption `json:"options" koanf:"options"`
	// Headers determines which headers are inspected to determine the origin IP.
	// Defaults to X-Forwarded-For, True-Client-IP, RemoteAddr.
	Headers []string `json:"headers" koanf:"headers" default:"[True-Client-IP]"`
	// ForwardedIndexFromBehind selects which IP from X-Forwarded-For should be used.
	// 0 means the closest client, 1 the proxy behind it, etc.
	ForwardedIndexFromBehind int `json:"forwardedindexfrombehind" koanf:"forwardedindexfrombehind" default:"0"`
	// IncludePath appends the request path to the limiter key when true.
	IncludePath bool `json:"includepath" koanf:"includepath" default:"false"`
	// IncludeMethod appends the request method to the limiter key when true.
	IncludeMethod bool `json:"includemethod" koanf:"includemethod" default:"false"`
	// KeyPrefix allows scoping the limiter key space with a static prefix.
	KeyPrefix string `json:"keyprefix" koanf:"keyprefix"`
	// DenyStatus overrides the HTTP status code returned when a rate limit is exceeded.
	DenyStatus int `json:"denystatus" koanf:"denystatus" default:"429"`
	// DenyMessage customises the error payload when a rate limit is exceeded.
	DenyMessage string `json:"denymessage" koanf:"denymessage" default:"Too many requests"`
	// SendRetryAfterHeader toggles whether the Retry-After header should be added when available.
	SendRetryAfterHeader bool `json:"sendretryafterheader" koanf:"sendretryafterheader" default:"true"`
	// DryRun enables logging rate limit decisions without blocking requests.
	DryRun bool `json:"dryrun" koanf:"dryrun" default:"true"`
}

Config defines the configuration settings for the rate limiter middleware.

type LimitStatus added in v0.43.0

type LimitStatus struct {
	// IsLimited is true when a given key should be rate-limited
	IsLimited bool
	// LimitDuration is the time for which a given key should be blocked
	LimitDuration *time.Duration
	// CurrentRate is approximated current requests rate per window size
	CurrentRate float64
}

LimitStatus represents current status of limitation for a given key

type LimitStore added in v0.43.0

type LimitStore interface {
	// Inc increments current window limit counter for key
	Inc(key string, window time.Time) error
	// Get gets value of previous window counter and current window counter for key
	Get(key string, previousWindow, currentWindow time.Time) (prevValue int64, currValue int64, err error)
}

LimitStore is the interface that represents limiter internal data store

type MapLimitStore added in v0.43.0

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

MapLimitStore represents a data structure for in-memory storage of ratelimiter information

func NewMapLimitStore added in v0.43.0

func NewMapLimitStore(expirationTime time.Duration, flushInterval time.Duration) (m *MapLimitStore)

NewMapLimitStore creates new in-memory data store for internal limiter data

func (*MapLimitStore) Get added in v0.43.0

func (m *MapLimitStore) Get(key string, previousWindow, currentWindow time.Time) (prevValue int64, currValue int64, err error)

Get gets value of previous window counter and current window counter

func (*MapLimitStore) Inc added in v0.43.0

func (m *MapLimitStore) Inc(key string, window time.Time) error

Inc increments current window limit counter

func (*MapLimitStore) Size added in v0.43.0

func (m *MapLimitStore) Size() int

Size returns current length of data map

type RateLimiter added in v0.43.0

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

RateLimiter is defining the structure of a rate limiter object

func New added in v0.43.0

func New(dataStore LimitStore, requestsLimit int64, windowSize time.Duration) *RateLimiter

New creates new rate limiter

func (*RateLimiter) Check added in v0.43.0

func (r *RateLimiter) Check(key string) (limitStatus *LimitStatus, err error)

Check checks status of the rate limit key

func (*RateLimiter) Inc added in v0.43.0

func (r *RateLimiter) Inc(key string) error

Inc increments the limit counter for a given key

func (*RateLimiter) RequestsLimit added in v0.43.0

func (r *RateLimiter) RequestsLimit() int64

RequestsLimit returns the maximum number of requests allowed within the window.

func (*RateLimiter) WindowSize added in v0.43.0

func (r *RateLimiter) WindowSize() time.Duration

WindowSize returns the duration of the configured window.

type RateOption added in v0.43.0

type RateOption struct {
	// Requests is the number of requests allowed within the configured window.
	Requests int64 `json:"requests" koanf:"requests" default:"500" example:"500"`
	// Window is the duration of the sliding window.
	Window time.Duration `json:"window" koanf:"window" default:"1m"`
	// Expiration controls how long counters are retained before eviction.
	// When unset, Expiration defaults to twice the Window duration.
	Expiration time.Duration `json:"expiration" koanf:"expiration"`
	// FlushInterval defines how frequently expired values are purged from memory.
	// When unset, FlushInterval defaults to 10s.
	FlushInterval time.Duration `json:"flushinterval" koanf:"flushinterval"`
}

RateOption defines a distinct rate limiting window and request allowance.

Jump to

Keyboard shortcuts

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