throttled

package module
v2.12.0 Latest Latest
Warning

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

Go to latest
Published: Jun 29, 2023 License: BSD-3-Clause Imports: 9 Imported by: 56

README

Throttled Build Status Go Reference

Package throttled implements rate limiting using the generic cell rate algorithm to limit access to resources such as HTTP endpoints.

The 2.0.0 release made some major changes to the throttled API. If this change broke your code in problematic ways or you wish a feature of the old API had been retained, please open an issue. We don't guarantee any particular changes but would like to hear more about what our users need. Thanks!

Installation

Go Modules are required to use Throttled (check that there's a go.mod in your package's root). Import Throttled:

import (
	"github.com/throttled/throttled/v2"
)

Then any of the standard Go tooling like go build, go test, will find the package automatically.

You can also pull it into your project using go get:

go get -u github.com/throttled/throttled/v2
Upgrading from the pre-Modules version

The current /v2 of Throttled is perfectly compatible with the pre-Modules version of Throttled, but when upgrading, you'll have to add /v2 to your imports. Sorry about the churn, but because Throttled was already on its semantic version 2 by the time Go Modules came around, its tooling didn't play nice because it expects the major version in the path to match the major in its tags.

Documentation

API documentation is available on godoc.org.

Usage

This example demonstrates the usage of HTTPLimiter for rate-limiting access to an http.Handler to 20 requests per path per minute with bursts of up to 5 additional requests:

package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/throttled/throttled/v2"
	"github.com/throttled/throttled/v2/store/memstore"
)

func myHandlerFunc(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello, world")
}

func main() {
	store, err := memstore.NewCtx(65536)
	if err != nil {
		log.Fatal(err)
	}

	quota := throttled.RateQuota{
		MaxRate:  throttled.PerMin(20),
		MaxBurst: 5,
	}
	rateLimiter, err := throttled.NewGCRARateLimiterCtx(store, quota)
	if err != nil {
		log.Fatal(err)
	}

	httpRateLimiter := throttled.HTTPRateLimiterCtx{
		RateLimiter: rateLimiter,
		VaryBy:      &throttled.VaryBy{Path: true},
	}

	handler := http.HandlerFunc(myHandlerFunc)
	http.ListenAndServe(":8080", httpRateLimiter.RateLimit(handler))
}
Upgrading to context.Context aware version of throttled

To upgrade to the new context.Context aware version of throttled, update the package to the latest version and replace the following function with their context-aware equivalent:

  • memstore.New => memstore.NewCtx
  • goredisstore.New => goredisstore.NewCtx
  • redigostore.New => redigostore.NewCtx
  • throttled.NewGCRARateLimiter => throttled.NewGCRARateLimiterCtx
  • throttled.HTTPRateLimiter => throttled.HTTPRateLimiterCtx

Please note that not all stores make use of the passed context.Context yet.

See throttled/gcra for a list of other projects related to rate limiting and GCRA.

Release

  1. Update CHANGELOG.md. Please use semantic versioning and the existing conventions established in the file. Commit the changes with a message like Bump version to 2.2.0.
  2. Tag master with a new version prefixed with v. For example, v2.2.0.
  3. git push origin master --tags.
  4. Publish a new release on the releases page. Copy the body from the contents of CHANGELOG.md for the version and follow other conventions from previous releases.

License

The BSD 3-clause license. Copyright (c) 2014 Martin Angers and contributors.

Documentation

Overview

Package throttled implements rate limiting access to resources such as HTTP endpoints.

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// DefaultDeniedHandler is the default DeniedHandler for an
	// HTTPRateLimiter. It returns a 429 status code with a generic
	// message.
	DefaultDeniedHandler = http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		http.Error(w, "limit exceeded", 429)
	}))

	// DefaultError is the default Error function for an HTTPRateLimiter.
	// It returns a 500 status code with a generic message.
	DefaultError = func(w http.ResponseWriter, r *http.Request, err error) {
		http.Error(w, "internal error", http.StatusInternalServerError)
	}
)

Functions

This section is empty.

Types

type GCRARateLimiterCtx added in v2.10.0

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

GCRARateLimiterCtx is a RateLimiter that uses the generic cell-rate algorithm. The algorithm has been slightly modified from its usual form to support limiting with an additional quantity parameter, such as for limiting the number of bytes uploaded.

Example

Demonstrates direct use of GCRARateLimiterCtx's RateLimit function (and the more general RateLimiter interface). This should be used anywhere where granular control over rate limiting is required.

store, err := memstore.NewCtx(65536)
if err != nil {
	log.Fatal(err)
}

// Maximum burst of 5 which refills at 1 token per hour.
quota := throttled.RateQuota{MaxRate: throttled.PerHour(1), MaxBurst: 5}

rateLimiter, err := throttled.NewGCRARateLimiterCtx(store, quota)
if err != nil {
	log.Fatal(err)
}

// Bucket according to the number i / 10 (so 1 falls into the bucket 0
// while 11 falls into the bucket 1). This has the effect of allowing a
// burst of 5 plus 1 (a single emission interval) on every ten iterations
// of the loop. See the output for better clarity here.
//
// We also refill the bucket at 1 token per hour, but that has no effect
// for the purposes of this example.
for i := 0; i < 20; i++ {
	bucket := fmt.Sprintf("by-order:%v", i/10)

	limited, result, err := rateLimiter.RateLimitCtx(context.Background(), bucket, 1)
	if err != nil {
		log.Fatal(err)
	}

	if limited {
		fmt.Printf("Iteration %2v; bucket %v: FAILED. Rate limit exceeded.\n",
			i, bucket)
	} else {
		fmt.Printf("Iteration %2v; bucket %v: Operation successful (remaining=%v).\n",
			i, bucket, result.Remaining)
	}
}
Output:

Iteration  0; bucket by-order:0: Operation successful (remaining=5).
Iteration  1; bucket by-order:0: Operation successful (remaining=4).
Iteration  2; bucket by-order:0: Operation successful (remaining=3).
Iteration  3; bucket by-order:0: Operation successful (remaining=2).
Iteration  4; bucket by-order:0: Operation successful (remaining=1).
Iteration  5; bucket by-order:0: Operation successful (remaining=0).
Iteration  6; bucket by-order:0: FAILED. Rate limit exceeded.
Iteration  7; bucket by-order:0: FAILED. Rate limit exceeded.
Iteration  8; bucket by-order:0: FAILED. Rate limit exceeded.
Iteration  9; bucket by-order:0: FAILED. Rate limit exceeded.
Iteration 10; bucket by-order:1: Operation successful (remaining=5).
Iteration 11; bucket by-order:1: Operation successful (remaining=4).
Iteration 12; bucket by-order:1: Operation successful (remaining=3).
Iteration 13; bucket by-order:1: Operation successful (remaining=2).
Iteration 14; bucket by-order:1: Operation successful (remaining=1).
Iteration 15; bucket by-order:1: Operation successful (remaining=0).
Iteration 16; bucket by-order:1: FAILED. Rate limit exceeded.
Iteration 17; bucket by-order:1: FAILED. Rate limit exceeded.
Iteration 18; bucket by-order:1: FAILED. Rate limit exceeded.
Iteration 19; bucket by-order:1: FAILED. Rate limit exceeded.

func NewGCRARateLimiter deprecated

func NewGCRARateLimiter(st GCRAStore, quota RateQuota) (*GCRARateLimiterCtx, error)

NewGCRARateLimiter is a backwards compatible adapter for NewGCRARateLimiterCtx.

Deprecated: Use NewGCRARateLimiterCtx instead. If the used store does not implement GCRAStoreCtx, wrap it with WrapStoreWithContext().

func NewGCRARateLimiterCtx added in v2.10.0

func NewGCRARateLimiterCtx(st GCRAStoreCtx, quota RateQuota) (*GCRARateLimiterCtx, error)

NewGCRARateLimiterCtx creates a GCRARateLimiterCtx. quota.Count defines the maximum number of requests permitted in an instantaneous burst and quota.Count / quota.Period defines the maximum sustained rate. For example, PerMin(60) permits 60 requests instantly per key followed by one request per second indefinitely whereas PerSec(1) only permits one request per second with no tolerance for bursts.

func (*GCRARateLimiterCtx) RateLimit deprecated added in v2.10.0

func (g *GCRARateLimiterCtx) RateLimit(key string, quantity int) (bool, RateLimitResult, error)

RateLimit is provided as a backwards compatible variant of RateLimitCtx.

Deprecated: Use RateLimitCtx instead.

func (*GCRARateLimiterCtx) RateLimitCtx added in v2.10.0

func (g *GCRARateLimiterCtx) RateLimitCtx(ctx context.Context, key string, quantity int) (bool, RateLimitResult, error)

RateLimitCtx checks whether a particular key has exceeded a rate limit. It also returns a RateLimitResult to provide additional information about the state of the RateLimiter.

If the rate limit has not been exceeded, the underlying storage is updated by the supplied quantity. For example, a quantity of 1 might be used to rate limit a single request while a greater quantity could rate limit based on the size of a file upload in megabytes. If quantity is 0, no update is performed allowing you to "peek" at the state of the RateLimiter for a given key.

func (*GCRARateLimiterCtx) SetMaxCASAttemptsLimit added in v2.10.0

func (g *GCRARateLimiterCtx) SetMaxCASAttemptsLimit(limit int)

SetMaxCASAttemptsLimit allows you to set the maxCASAttempts limit. This is set to 10 be default.

type GCRAStore deprecated

type GCRAStore interface {
	GetWithTime(key string) (int64, time.Time, error)
	SetIfNotExistsWithTTL(key string, value int64, ttl time.Duration) (bool, error)
	CompareAndSwapWithTTL(key string, old, new int64, ttl time.Duration) (bool, error)
}

GCRAStore is the version of GCRAStoreCtx that is not aware of context.

Deprecated: Implement GCRAStoreCtx instead.

type GCRAStoreCtx added in v2.10.0

type GCRAStoreCtx interface {
	// GetWithTime returns the value of the key if it is in the store
	// or -1 if it does not exist. It also returns the current time at
	// the Store. The time must be representable as a positive int64
	// of nanoseconds since the epoch.
	//
	// GCRA assumes that all instances sharing the same Store also
	// share the same clock. Using separate clocks will work if the
	// skew is small but not recommended in practice unless you're
	// lucky enough to be hooked up to GPS or atomic clocks.
	GetWithTime(ctx context.Context, key string) (int64, time.Time, error)

	// SetIfNotExistsWithTTL sets the value of key only if it is not
	// already set in the store it returns whether a new value was
	// set. If the store supports expiring keys and a new value was
	// set, the key will expire after the provided ttl.
	SetIfNotExistsWithTTL(ctx context.Context, key string, value int64, ttl time.Duration) (bool, error)

	// CompareAndSwapWithTTL atomically compares the value at key to
	// the old value. If it matches, it sets it to the new value and
	// returns true. Otherwise, it returns false. If the key does not
	// exist in the store, it returns false with no error. If the
	// store supports expiring keys and the swap succeeded, the key
	// will expire after the provided ttl.
	CompareAndSwapWithTTL(ctx context.Context, key string, old, new int64, ttl time.Duration) (bool, error)
}

GCRAStoreCtx is the interface to implement to store state for a GCRA rate limiter that uses a context.Context

func WrapStoreWithContext added in v2.10.0

func WrapStoreWithContext(store GCRAStore) GCRAStoreCtx

WrapStoreWithContext can be used to use GCRAStore in a place where a GCRAStoreCtx is required.

type HTTPRateLimiter deprecated

type HTTPRateLimiter struct {
	// DeniedHandler is called if the request is disallowed. If it is
	// nil, the DefaultDeniedHandler variable is used.
	DeniedHandler http.Handler

	// Error is called if the RateLimiter returns an error. If it is
	// nil, the DefaultErrorFunc is used.
	Error func(w http.ResponseWriter, r *http.Request, err error)

	// Limiter is call for each request to determine whether the
	// request is permitted and update internal state. It must be set.
	RateLimiter RateLimiter

	// VaryBy is called for each request to generate a key for the
	// limiter. If it is nil, all requests use an empty string key.
	VaryBy interface {
		Key(*http.Request) string
	}
}

HTTPRateLimiter is an adapter for HTTPRateLimiterCtx to provide backwards compatibility.

Deprecated: Use HTTPRateLimiterCtx instead. If the used RateLimiter does not implement RateLimiterCtx, wrap it with WrapRateLimiterWithContext().

func (*HTTPRateLimiter) RateLimit deprecated

func (t *HTTPRateLimiter) RateLimit(h http.Handler) http.Handler

RateLimit provides an adapter for HTTPRateLimiterCtx.RateLimit.

Deprecated: Use HTTPRateLimiterCtx instead

type HTTPRateLimiterCtx added in v2.10.0

type HTTPRateLimiterCtx struct {
	// DeniedHandler is called if the request is disallowed. If it is
	// nil, the DefaultDeniedHandler variable is used.
	DeniedHandler http.Handler

	// Error is called if the RateLimiter returns an error. If it is
	// nil, the DefaultErrorFunc is used.
	Error func(w http.ResponseWriter, r *http.Request, err error)

	// Limiter is call for each request to determine whether the
	// request is permitted and update internal state. It must be set.
	RateLimiter RateLimiterCtx

	// VaryBy is called for each request to generate a key for the
	// limiter. If it is nil, all requests use an empty string key.
	VaryBy interface {
		Key(*http.Request) string
	}
}

HTTPRateLimiterCtx facilitates using a Limiter to limit HTTP requests.

Example

ExampleHTTPRateLimiter demonstrates the usage of HTTPRateLimiterCtx for rate-limiting access to an http.Handler to 20 requests per path per minute with a maximum burst of 5 requests.

store, err := memstore.NewCtx(65536)
if err != nil {
	log.Fatal(err)
}

// Maximum burst of 5 which refills at 20 tokens per minute.
quota := throttled.RateQuota{MaxRate: throttled.PerMin(20), MaxBurst: 5}

rateLimiter, err := throttled.NewGCRARateLimiterCtx(store, quota)
if err != nil {
	log.Fatal(err)
}

httpRateLimiter := throttled.HTTPRateLimiterCtx{
	RateLimiter: rateLimiter,
	VaryBy:      &throttled.VaryBy{Path: true},
}

http.ListenAndServe(":8080", httpRateLimiter.RateLimit(myHandler))
Output:

func (*HTTPRateLimiterCtx) RateLimit added in v2.10.0

func (t *HTTPRateLimiterCtx) RateLimit(h http.Handler) http.Handler

RateLimit wraps an http.Handler to limit incoming requests. Requests that are not limited will be passed to the handler unchanged. Limited requests will be passed to the DeniedHandler. X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset and Retry-After headers will be written to the response based on the values in the RateLimitResult.

type Q deprecated

type Q struct {
	Requests int
	Window   time.Duration
}

Q represents a custom quota.

Deprecated: Use Rate and RateLimiter instead.

func (Q) Quota deprecated

func (q Q) Quota() (int, time.Duration)

Quota returns the number of requests allowed and the custom time window.

Deprecated: Use Rate and RateLimiter instead.

type Quota deprecated

type Quota interface {
	// Quota returns a number of requests allowed, and a duration.
	Quota() (int, time.Duration)
}

The Quota interface defines the method to implement to describe a time-window quota, as required by the RateLimit throttler.

Deprecated: Use Rate and RateLimiter instead.

type Rate

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

Rate describes a frequency of an activity such as the number of requests allowed per minute.

func PerDay

func PerDay(n int) Rate

PerDay represents a number of requests per day.

func PerDuration added in v2.6.0

func PerDuration(n int, d time.Duration) Rate

PerDuration represents a number of requests per provided duration.

func PerHour

func PerHour(n int) Rate

PerHour represents a number of requests per hour.

func PerMin

func PerMin(n int) Rate

PerMin represents a number of requests per minute.

func PerSec

func PerSec(n int) Rate

PerSec represents a number of requests per second.

func (Rate) Quota deprecated

func (q Rate) Quota() (int, time.Duration)

Quota returns the number of requests allowed and the custom time window.

Deprecated: Use Rate and RateLimiter instead.

type RateLimitResult

type RateLimitResult struct {
	// Limit is the maximum number of requests that could be permitted
	// instantaneously for this key starting from an empty state. For
	// example, if a rate limiter allows 10 requests per second per
	// key, Limit would always be 10.
	Limit int

	// Remaining is the maximum number of requests that could be
	// permitted instantaneously for this key given the current
	// state. For example, if a rate limiter allows 10 requests per
	// second and has already received 6 requests for this key this
	// second, Remaining would be 4.
	Remaining int

	// ResetAfter is the time until the RateLimiter returns to its
	// initial state for a given key. For example, if a rate limiter
	// manages requests per second and received one request 200ms ago,
	// Reset would return 800ms. You can also think of this as the time
	// until Limit and Remaining will be equal.
	ResetAfter time.Duration

	// RetryAfter is the time until the next request will be permitted.
	// It should be -1 unless the rate limit has been exceeded.
	RetryAfter time.Duration
}

RateLimitResult represents the state of the RateLimiter for a given key at the time of the query. This state can be used, for example, to communicate information to the client via HTTP headers. Negative values indicate that the attribute is not relevant to the implementation or state.

type RateLimiter deprecated

type RateLimiter interface {
	// RateLimit checks whether a particular key has exceeded a rate
	// limit. It also returns a RateLimitResult to provide additional
	// information about the state of the RateLimiter.
	//
	// If the rate limit has not been exceeded, the underlying storage
	// is updated by the supplied quantity. For example, a quantity of
	// 1 might be used to rate limit a single request while a greater
	// quantity could rate limit based on the size of a file upload in
	// megabytes. If quantity is 0, no update is performed allowing
	// you to "peek" at the state of the RateLimiter for a given key.
	RateLimit(key string, quantity int) (bool, RateLimitResult, error)
}

A RateLimiter manages limiting the rate of actions by key.

Deprecated: Use RateLimiterCtx instead.

type RateLimiterCtx added in v2.10.0

type RateLimiterCtx interface {
	// RateLimitCtx checks whether a particular key has exceeded a rate
	// limit. It also returns a RateLimitResult to provide additional
	// information about the state of the RateLimiter.
	//
	// If the rate limit has not been exceeded, the underlying storage
	// is updated by the supplied quantity. For example, a quantity of
	// 1 might be used to rate limit a single request while a greater
	// quantity could rate limit based on the size of a file upload in
	// megabytes. If quantity is 0, no update is performed allowing
	// you to "peek" at the state of the RateLimiter for a given key.
	RateLimitCtx(ctx context.Context, key string, quantity int) (bool, RateLimitResult, error)
}

A RateLimiterCtx manages limiting the rate of actions by key.

func WrapRateLimiterWithContext added in v2.10.0

func WrapRateLimiterWithContext(rateLimier RateLimiter) RateLimiterCtx

WrapRateLimiterWithContext can be used to use RateLimiter in a place where a RateLimiterCtx is required.

type RateQuota

type RateQuota struct {
	MaxRate  Rate
	MaxBurst int
}

RateQuota describes the number of requests allowed per time period. MaxRate specified the maximum sustained rate of requests and must be greater than zero. MaxBurst defines the number of requests that will be allowed to exceed the rate in a single burst and must be greater than or equal to zero.

Rate{PerSec(1), 0} would mean that after each request, no more requests will be permitted for that client for one second. Rate{PerSec(2), 0} permits one request per 0.5 seconds rather than two requests in one second. In practice, you probably want to set MaxBurst >0 to provide some flexibility to clients that only need to make a handful of requests. In fact a MaxBurst of zero will *never* permit a request with a quantity greater than one because it will immediately exceed the limit.

type Store deprecated

type Store interface {
	GCRAStore
}

Store is an alias for GCRAStore

Deprecated: Use Rate and RateLimiter instead.

type Throttler deprecated

type Throttler struct {
	HTTPRateLimiter
}

Throttler is a backwards-compatible alias for HTTPLimiter.

Deprecated: Use Rate and RateLimiter instead.

func RateLimit deprecated

func RateLimit(q Quota, vary *VaryBy, store GCRAStore) *Throttler

RateLimit creates a Throttler that conforms to the given rate limits

Deprecated: Use Rate and RateLimiter instead.

func (*Throttler) Throttle deprecated

func (t *Throttler) Throttle(h http.Handler) http.Handler

Throttle is an alias for HTTPLimiter#Limit

Deprecated: Use Rate and RateLimiter instead.

type VaryBy

type VaryBy struct {
	// Vary by the RemoteAddr as specified by the net/http.Request field.
	RemoteAddr bool

	// Vary by the HTTP Method as specified by the net/http.Request field.
	Method bool

	// Vary by the URL's Path as specified by the Path field of the net/http.Request
	// URL field.
	Path bool

	// Vary by this list of header names, read from the net/http.Request Header field.
	Headers []string

	// Vary by this list of parameters, read from the net/http.Request FormValue method.
	Params []string

	// Vary by this list of cookie names, read from the net/http.Request Cookie method.
	Cookies []string

	// Use this separator string to concatenate the various criteria of the VaryBy struct.
	// Defaults to a newline character if empty (\n).
	Separator string

	// DEPRECATED. Custom specifies the custom-generated key to use for this request.
	// If not nil, the value returned by this function is used instead of any
	// VaryBy criteria.
	Custom func(r *http.Request) string
}

VaryBy defines the criteria to use to group requests.

func (*VaryBy) Key

func (vb *VaryBy) Key(r *http.Request) string

Key returns the key for this request based on the criteria defined by the VaryBy struct.

Directories

Path Synopsis
Package store contains deprecated aliases for subpackages
Package store contains deprecated aliases for subpackages
goredisstore
Package goredisstore offers Redis-based store implementation for throttled using go-redis.
Package goredisstore offers Redis-based store implementation for throttled using go-redis.
goredisstore.v8
Package goredisstore offers Redis-based store implementation for throttled using v8 of go-redis.
Package goredisstore offers Redis-based store implementation for throttled using v8 of go-redis.
goredisstore.v9
Package goredisstore offers Redis-based store implementation for throttled using v9 of go-redis.
Package goredisstore offers Redis-based store implementation for throttled using v9 of go-redis.
memstore
Package memstore offers an in-memory store implementation for throttled.
Package memstore offers an in-memory store implementation for throttled.
redigostore
Package redigostore offers Redis-based store implementation for throttled using redigo.
Package redigostore offers Redis-based store implementation for throttled using redigo.
storetest
Package storetest provides a helper for testing throttled stores.
Package storetest provides a helper for testing throttled stores.

Jump to

Keyboard shortcuts

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