Skip to content

Commit

Permalink
Merge pull request #23 from ulule/v2
Browse files Browse the repository at this point in the history
Release v2.0.0
  • Loading branch information
thoas authored Oct 10, 2017
2 parents 619f3ae + 2856f7c commit fe4c297
Show file tree
Hide file tree
Showing 41 changed files with 1,653 additions and 833 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/vendor
28 changes: 19 additions & 9 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
language: go
before_install:
- go get github.com/stretchr/testify
- go get github.com/garyburd/redigo/redis
- go get github.com/ant0ine/go-json-rest/rest
- go get github.com/patrickmn/go-cache
- go get -u github.com/golang/dep/cmd/dep
- dep ensure
- go get -u github.com/alecthomas/gometalinter
- gometalinter --install
go:
- 1.4
- 1.5
- 1.6
- 1.7
- 1.8
script: make test
- 1.8.1
- 1.8.2
- 1.8.3
- 1.9
- 1.9.1
- tip
script:
- make test
- make lint
services:
- redis-server
env:
- REDIS_DISABLE_BOOTSTRAP=true
matrix:
fast_finish: true
allow_failures:
- go: tip
5 changes: 3 additions & 2 deletions AUTHORS
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
Primary contributors:

Gilles Fabio <[email protected]>
Florent Messa <[email protected]>
Gilles FABIO <[email protected]>
Florent MESSA <[email protected]>
Thomas LE ROUX <[email protected]>
87 changes: 87 additions & 0 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions Gopkg.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Gopkg.toml for github.com/ulule/limiter

[[constraint]]
name = "github.com/pkg/errors"
version = "0.8.0"

[[constraint]]
name = "github.com/go-redis/redis"
version = "6.5.6"

[[constraint]]
name = "github.com/gin-gonic/gin"
version = "v1.2"
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,7 @@ cleandb:
@(redis-cli KEYS "limitertests:*" | xargs redis-cli DEL)

test: cleandb
@(go test -v -run ^Test)
@(scripts/test)

lint:
@(scripts/lint)
51 changes: 30 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* Simple API
* "Store" approach for backend
* Redis support (but not tied too)
* Middlewares: HTTP and [go-json-rest][2]
* Middlewares: HTTP and [Gin][4]

## Installation

Expand All @@ -22,20 +22,22 @@ $ go get github.com/ulule/limiter

In five steps:

* Create a `limiter.Rate` instance (the number of requests per period)
* Create a `limiter.Store` instance (see [store_redis](https://github.com/ulule/limiter/blob/master/store_redis.go) for Redis or [store_memory](https://github.com/ulule/limiter/blob/master/store_memory.go) for in-memory)
* 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:
**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"

rate := limiter.Rate{
Period: 1 * time.Hour,
Limit: int64(1000),
Limit: 1000,
}

// You can also use the simplified format "<limit>-<period>"", with the given
Expand All @@ -60,34 +62,39 @@ if err != nil {
// 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.
store, err := limiter.NewRedisStore(pool)
import "github.com/ulule/limiter/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:
store, err := limiter.NewRedisStoreWithOptions(pool, limiter.StoreOptions{
import "github.com/ulule/limiter/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 every 30 seconds
store := limiter.NewMemoryStore("prefix_for_keys", 30*time.Second)
// Or use a in-memory store with a goroutine which clears expired keys.
import "github.com/ulule/limiter/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.
limiterInstance := limiter.NewLimiter(store, rate)
instance := limiter.New(store, rate)
```

See middleware examples:

* [HTTP](https://github.com/ulule/limiter/tree/master/examples/http)
* [go-json-rest](https://github.com/ulule/limiter/tree/master/examples/gjr)
* [HTTP](https://github.com/ulule/limiter/tree/master/examples/http/main.go)
* [Gin](https://github.com/ulule/limiter/tree/master/examples/gin/main.go)

## How it works

Expand All @@ -98,10 +105,10 @@ value with an expiration period.

You will find two stores:

* RedisStore: rely on [TTL](http://redis.io/commands/ttl) and incrementing the rate limit on each request
* MemoryStore: rely on [go-cache](https://github.com/patrickmn/go-cache) with a goroutine to clear expired keys using a default interval
* 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 code is sent.
When the limit is reached, a `429` HTTP status code is sent.

## Why Yet Another Package

Expand All @@ -118,7 +125,7 @@ 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 [go-json-rest][2]). Currently, we only need to return `429`
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,
Expand All @@ -131,7 +138,7 @@ provide any Redis support (only *in-memory*) and a ready-to-go middleware that s
`X-Ratelimit-*` headers. `tollbooth.LimitByRequest(limiter, r)` only returns an HTTP
code.

4. [ratelimit][6]. Probably the closer to our needs but, once again, too
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.
Expand All @@ -142,18 +149,20 @@ create yet another one.

## Contributing

* Ping us on twitter [@oibafsellig](https://twitter.com/oibafsellig), [@thoas](https://twitter.com/thoas)
* 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/ant0ine/go-json-rest
[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
[6]: https://github.com/r8k/ratelimit

[godoc-url]: https://godoc.org/github.com/ulule/limiter
[godoc-img]: https://godoc.org/github.com/ulule/limiter?status.svg
Expand Down
55 changes: 55 additions & 0 deletions drivers/middleware/gin/middleware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package gin

import (
"strconv"

"github.com/gin-gonic/gin"

"github.com/ulule/limiter"
)

// Middleware is the middleware for basic http.Handler.
type Middleware struct {
Limiter *limiter.Limiter
OnError ErrorHandler
OnLimitReached LimitReachedHandler
}

// NewMiddleware return a new instance of a basic HTTP middleware.
func NewMiddleware(limiter *limiter.Limiter, options ...Option) gin.HandlerFunc {
middleware := &Middleware{
Limiter: limiter,
OnError: DefaultErrorHandler,
OnLimitReached: DefaultLimitReachedHandler,
}

for _, option := range options {
option.apply(middleware)
}

return func(ctx *gin.Context) {
middleware.Handle(ctx)
}
}

// Handle gin request.
func (middleware *Middleware) Handle(c *gin.Context) {
context, err := middleware.Limiter.Get(c, limiter.GetIPKey(c.Request))
if err != nil {
middleware.OnError(c, err)
c.Abort()
return
}

c.Header("X-RateLimit-Limit", strconv.FormatInt(context.Limit, 10))
c.Header("X-RateLimit-Remaining", strconv.FormatInt(context.Remaining, 10))
c.Header("X-RateLimit-Reset", strconv.FormatInt(context.Reset, 10))

if context.Reached {
middleware.OnLimitReached(c)
c.Abort()
return
}

c.Next()
}
Loading

0 comments on commit fe4c297

Please sign in to comment.