ratelimiter

package
v0.2.3 Latest Latest
Warning

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

Go to latest
Published: Oct 7, 2024 License: Apache-2.0 Imports: 3 Imported by: 0

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", "X-Real-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)

Documentation

Overview

Package ratelimiter is a ratelimiter based on cloudflare's approach

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type LimitStatus

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

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

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

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

func NewMapLimitStore

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

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

func (*MapLimitStore) Get

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

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

Inc increments current window limit counter

func (*MapLimitStore) Size

func (m *MapLimitStore) Size() int

Size returns current length of data map

type RateLimiter

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

RateLimiter is defining the structure of a rate limiter object

func New

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

New creates new rate limiter

func (*RateLimiter) Check

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

Check checks status of the rate limit key

func (*RateLimiter) Inc

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

Inc increments the limit counter for a given key

Directories

Path Synopsis
examples

Jump to

Keyboard shortcuts

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