ratelimiter

package module
v0.2.1 Latest Latest
Warning

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

Go to latest
Published: Mar 21, 2019 License: MIT Imports: 3 Imported by: 2

README

ratelimiter

go-doc Go Report Card

Simple rate limiter for any resources inspired by Cloudflare's approach: How we built rate limiting capable of scaling to millions of domains.

Usage

Getting started
package main

import (
	"fmt"
	"log"
	"time"

	"github.com/coinpaprika/ratelimiter"
)

func main() {
	limitedKey := "key"
	windowSize := 1 * time.Minute

	dataStore := ratelimiter.NewMapLimitStore(2*windowSize, 10*time.Second) // 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

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

	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
			} else {
				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
	dataStore := ratelimiter.NewMapLimitStore(2*windowSize, 10*time.Second) // 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
	rateLimiter := ratelimiter.New(dataStore, 5, windowSize)                // allow 5 requests per windowSize (1 minute)

	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

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

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)

Examples

Check out the examples directory.

License

ratelimiter is available under the MIT license. See the LICENSE file for more info.

Documentation

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 not nil when IsLimited is true. It's the time for which a given key should be blocked before CurrentRate falls below declared in constructor requests limit
	LimitDuration *time.Duration
	// CurrentRate is approximated current requests rate per window size (declared in the constructor)
	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. Any database struct that implements LimitStore should have functions for incrementing counter of a given key and getting counter values of a given key for previous and current window

type MapLimitStore

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

MapLimitStore represents internal limiter data database where data are stored in golang maps

func NewMapLimitStore

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

NewMapLimitStore creates new in-memory data store for internal limiter data. Each element of MapLimitStore is set as expired after expirationTime from its last counter increment. Expired elements are removed with a period specified by the flushInterval argument

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 for key

func (*MapLimitStore) Inc

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

Inc increments current window limit counter for key

func (*MapLimitStore) Size added in v0.2.0

func (m *MapLimitStore) Size() int

Size returns current length of data map

type RateLimiter

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

RateLimiter is a simple rate-limiter for any resources inspired by Cloudflare's approach: https://blog.cloudflare.com/counting-things-a-lot-of-different-things/

func New

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

New creates new rate limiter. A dataStore is internal limiter data store, requestsLimit and windowSize are parameters of limiter e.g. requestsLimit: 5 and windowSize: 1*time.Minute means that limiter allows up to 5 requests per minute

func (*RateLimiter) Check

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

Check checks status of rate-limiting for a key. It returns error when limiter data could not be read

func (*RateLimiter) Inc

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

Inc increments limiter counter for a given key or returns error when it's not possible

Directories

Path Synopsis
examples

Jump to

Keyboard shortcuts

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