185 lines
6.4 KiB
Markdown
185 lines
6.4 KiB
Markdown
# Limiter
|
|
|
|
[![Documentation][godoc-img]][godoc-url]
|
|
![License][license-img]
|
|
[![Build Status][circle-img]][circle-url]
|
|
[![Go Report Card][goreport-img]][goreport-url]
|
|
|
|
*Dead simple rate limit middleware for Go.*
|
|
|
|
* Simple API
|
|
* "Store" approach for backend
|
|
* Redis support (but not tied too)
|
|
* Middlewares: HTTP and [Gin][4]
|
|
|
|
## Installation
|
|
|
|
Using [Go Modules](https://github.com/golang/go/wiki/Modules)
|
|
|
|
```bash
|
|
$ go get github.com/ulule/limiter/v3@v3.2.0
|
|
```
|
|
|
|
**Dep backport:**
|
|
|
|
Please use [v3-dep](https://github.com/ulule/limiter/tree/v3-dep) branch.
|
|
|
|
## Usage
|
|
|
|
In five steps:
|
|
|
|
* Create a `limiter.Rate` instance _(the number of requests per period)_
|
|
* Create a `limiter.Store` instance _(see [Redis](https://github.com/ulule/limiter/blob/master/drivers/store/redis/store.go) or [In-Memory](https://github.com/ulule/limiter/blob/master/drivers/store/memory/store.go))_
|
|
* Create a `limiter.Limiter` instance that takes store and rate instances as arguments
|
|
* Create a middleware instance using the middleware of your choice
|
|
* Give the limiter instance to your middleware initializer
|
|
|
|
**Example:**
|
|
|
|
```go
|
|
// Create a rate with the given limit (number of requests) for the given
|
|
// period (a time.Duration of your choice).
|
|
import "github.com/ulule/limiter/v3"
|
|
|
|
rate := limiter.Rate{
|
|
Period: 1 * time.Hour,
|
|
Limit: 1000,
|
|
}
|
|
|
|
// You can also use the simplified format "<limit>-<period>"", with the given
|
|
// periods:
|
|
//
|
|
// * "S": second
|
|
// * "M": minute
|
|
// * "H": hour
|
|
// * "D": day
|
|
//
|
|
// Examples:
|
|
//
|
|
// * 5 reqs/second: "5-S"
|
|
// * 10 reqs/minute: "10-M"
|
|
// * 1000 reqs/hour: "1000-H"
|
|
// * 2000 reqs/day: "2000-D"
|
|
//
|
|
rate, err := limiter.NewRateFromFormatted("1000-H")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// Then, create a store. Here, we use the bundled Redis store. Any store
|
|
// compliant to limiter.Store interface will do the job. The defaults are
|
|
// "limiter" as Redis key prefix and a maximum of 3 retries for the key under
|
|
// race condition.
|
|
import "github.com/ulule/limiter/v3/drivers/store/redis"
|
|
|
|
store, err := redis.NewStore(client)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// Alternatively, you can pass options to the store with the "WithOptions"
|
|
// function. For example, for Redis store:
|
|
import "github.com/ulule/limiter/v3/drivers/store/redis"
|
|
|
|
store, err := redis.NewStoreWithOptions(pool, limiter.StoreOptions{
|
|
Prefix: "your_own_prefix",
|
|
MaxRetry: 4,
|
|
})
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// Or use a in-memory store with a goroutine which clears expired keys.
|
|
import "github.com/ulule/limiter/v3/drivers/store/memory"
|
|
|
|
store := memory.NewStore()
|
|
|
|
// Then, create the limiter instance which takes the store and the rate as arguments.
|
|
// Now, you can give this instance to any supported middleware.
|
|
instance := limiter.New(store, rate)
|
|
```
|
|
|
|
See middleware examples:
|
|
|
|
* [HTTP](https://github.com/ulule/limiter/tree/master/examples/http/main.go)
|
|
* [Gin](https://github.com/ulule/limiter/tree/master/examples/gin/main.go)
|
|
* [Beego](https://github.com/ulule/limiter/blob/master/examples/beego/main.go)
|
|
* [Chi](https://github.com/ulule/limiter/tree/master/examples/chi/main.go)
|
|
* [Echo](https://github.com/ulule/limiter/tree/master/examples/echo/main.go)
|
|
|
|
|
|
## How it works
|
|
|
|
The ip address of the request is used as a key in the store.
|
|
|
|
If the key does not exist in the store we set a default
|
|
value with an expiration period.
|
|
|
|
You will find two stores:
|
|
|
|
* Redis: rely on [TTL](http://redis.io/commands/ttl) and incrementing the rate limit on each request.
|
|
* In-Memory: rely on a fork of [go-cache](https://github.com/patrickmn/go-cache) with a goroutine to clear expired keys using a default interval.
|
|
|
|
When the limit is reached, a `429` HTTP status code is sent.
|
|
|
|
## Why Yet Another Package
|
|
|
|
You could ask us: why yet another rate limit package?
|
|
|
|
Because existing packages did not suit our needs.
|
|
|
|
We tried a lot of alternatives:
|
|
|
|
1. [Throttled][1]. This package uses the generic cell-rate algorithm. To cite the
|
|
documentation: *"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"*. It is brillant in term of algorithm but
|
|
documentation is quite unclear at the moment, we don't need *burst* feature for
|
|
now, impossible to get a correct `After-Retry` (when limit exceeds, we can still
|
|
make a few requests, because of the max burst) and it only supports ``http.Handler``
|
|
middleware (we use [Gin][4]). Currently, we only need to return `429`
|
|
and `X-Ratelimit-*` headers for `n reqs/duration`.
|
|
|
|
2. [Speedbump][3]. Good package but maybe too lightweight. No `Reset` support,
|
|
only one middleware for [Gin][4] framework and too Redis-coupled. We rather
|
|
prefer to use a "store" approach.
|
|
|
|
3. [Tollbooth][5]. Good one too but does both too much and too little. It limits by
|
|
remote IP, path, methods, custom headers and basic auth usernames... but does not
|
|
provide any Redis support (only *in-memory*) and a ready-to-go middleware that sets
|
|
`X-Ratelimit-*` headers. `tollbooth.LimitByRequest(limiter, r)` only returns an HTTP
|
|
code.
|
|
|
|
4. [ratelimit][2]. Probably the closer to our needs but, once again, too
|
|
lightweight, no middleware available and not active (last commit was in August
|
|
2014). Some parts of code (Redis) comes from this project. It should deserve much
|
|
more love.
|
|
|
|
There are other many packages on GitHub but most are either too lightweight, too
|
|
old (only support old Go versions) or unmaintained. So that's why we decided to
|
|
create yet another one.
|
|
|
|
## Contributing
|
|
|
|
* Ping us on twitter:
|
|
* [@oibafsellig](https://twitter.com/oibafsellig)
|
|
* [@thoas](https://twitter.com/thoas)
|
|
* [@novln_](https://twitter.com/novln_)
|
|
* Fork the [project](https://github.com/ulule/limiter)
|
|
* Fix [bugs](https://github.com/ulule/limiter/issues)
|
|
|
|
Don't hesitate ;)
|
|
|
|
[1]: https://github.com/throttled/throttled
|
|
[2]: https://github.com/r8k/ratelimit
|
|
[3]: https://github.com/etcinit/speedbump
|
|
[4]: https://github.com/gin-gonic/gin
|
|
[5]: https://github.com/didip/tollbooth
|
|
|
|
[godoc-url]: https://godoc.org/github.com/ulule/limiter
|
|
[godoc-img]: https://godoc.org/github.com/ulule/limiter?status.svg
|
|
[license-img]: https://img.shields.io/badge/license-MIT-blue.svg
|
|
[goreport-url]: https://goreportcard.com/report/github.com/ulule/limiter
|
|
[goreport-img]: https://goreportcard.com/badge/github.com/ulule/limiter
|
|
[circle-url]: https://circleci.com/gh/ulule/limiter/tree/master
|
|
[circle-img]: https://circleci.com/gh/ulule/limiter.svg?style=shield&circle-token=baf62ec320dd871b3a4a7e67fa99530fbc877c99
|