mirror of
https://github.com/kataras/iris.git
synced 2025-02-02 15:30:36 +01:00
New Rate Limit middleware (still WIP though)
Former-commit-id: 99e282e4d400c83a56a808212d812cd701e1bcd8
This commit is contained in:
parent
f667bc5ff3
commit
3775189de8
6
FAQ.md
6
FAQ.md
|
@ -47,12 +47,12 @@ open for Iris-specific developers the time we speak.
|
|||
|
||||
Go to our facebook page, like it and receive notifications about new job offers, we already have couple of them stay at the top of the page: https://www.facebook.com/iris.framework
|
||||
|
||||
## Do we have a community Chat?
|
||||
## Do we have a Community chat?
|
||||
|
||||
Yes, https://chat.iris-go.com
|
||||
|
||||
## How is the development of Iris supported?
|
||||
## How is the development of Iris economically supported?
|
||||
|
||||
By normal people, like you, who help us by donating small or large amounts of money.
|
||||
By people like you, who help us by donating small or large amounts of money.
|
||||
|
||||
Help this project deliver awesome and unique features with the highest possible code quality by donating any amount via [PayPal](https://www.paypal.me/kataras). Your name will be published [here](https://iris-go.com) after your approval via e-mail.
|
||||
|
|
|
@ -283,6 +283,7 @@ You can serve [quicktemplate](https://github.com/valyala/quicktemplate) and [her
|
|||
|
||||
### Miscellaneous
|
||||
|
||||
- [Rate Limit](miscellaneous/ratelimit/main.go) **NEW**
|
||||
- [HTTP Method Override](https://github.com/kataras/iris/blob/master/middleware/methodoverride/methodoverride_test.go)
|
||||
- [Request Logger](http_request/request-logger/main.go)
|
||||
* [log requests to a file](http_request/request-logger/request-logger-file/main.go)
|
||||
|
@ -290,6 +291,7 @@ You can serve [quicktemplate](https://github.com/valyala/quicktemplate) and [her
|
|||
- [Profiling (pprof)](miscellaneous/pprof/main.go)
|
||||
- [Internal Application File Logger](miscellaneous/file-logger/main.go)
|
||||
- [Google reCAPTCHA](miscellaneous/recaptcha/main.go)
|
||||
- [hCaptcha](miscellaneous/hcaptcha/main.go) **NEW**
|
||||
|
||||
### Community-based Handlers
|
||||
|
||||
|
|
47
_examples/miscellaneous/ratelimit/main.go
Normal file
47
_examples/miscellaneous/ratelimit/main.go
Normal file
|
@ -0,0 +1,47 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/kataras/iris/v12"
|
||||
"github.com/kataras/iris/v12/middleware/rate"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := newApp()
|
||||
app.Logger().SetLevel("debug")
|
||||
|
||||
app.Listen(":8080")
|
||||
}
|
||||
|
||||
func newApp() *iris.Application {
|
||||
app := iris.New()
|
||||
|
||||
// Register the rate limiter middleware at the root router.
|
||||
//
|
||||
// Fist and second input parameters:
|
||||
// Allow 1 request per second, with a maximum burst size of 5.
|
||||
//
|
||||
// Third optional variadic input parameter:
|
||||
// Can be a cleanup function.
|
||||
// Iris provides a cleanup function that will check for old entries and remove them.
|
||||
// You can customize it, e.g. check every 1 minute
|
||||
// if a client's last visit was 5 minutes ago ("old" entry)
|
||||
// and remove it from the memory.
|
||||
rateLimiter := rate.Limit(1, 5, rate.PurgeEvery(time.Minute, 5*time.Minute))
|
||||
app.Use(rateLimiter)
|
||||
|
||||
// Routes.
|
||||
app.Get("/", index)
|
||||
app.Get("/other", other)
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
func index(ctx iris.Context) {
|
||||
ctx.HTML("<h1>Index Page</h1>")
|
||||
}
|
||||
|
||||
func other(ctx iris.Context) {
|
||||
ctx.HTML("<h1>Other Page</h1>")
|
||||
}
|
1
go.mod
1
go.mod
|
@ -33,6 +33,7 @@ require (
|
|||
go.etcd.io/bbolt v1.3.4
|
||||
golang.org/x/crypto v0.0.0-20200427165652-729f1e841bcc
|
||||
golang.org/x/text v0.3.2
|
||||
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1
|
||||
gopkg.in/ini.v1 v1.55.0
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
|
||||
)
|
||||
|
|
|
@ -10,6 +10,7 @@ Builtin Handlers
|
|||
| [Google reCAPTCHA](recaptcha) | [iris/_examples/miscellaneous/recaptcha](https://github.com/kataras/iris/tree/master/_examples/miscellaneous/recaptcha) |
|
||||
| [hCaptcha](hcaptcha) | [iris/_examples/miscellaneous/recaptcha](https://github.com/kataras/iris/tree/master/_examples/miscellaneous/hcaptcha) |
|
||||
| [recovery](recover) | [iris/_examples/miscellaneous/recover](https://github.com/kataras/iris/tree/master/_examples/miscellaneous/recover) |
|
||||
| [rate](rate) | [iris/_examples/miscellaneous/ratelimit](https://github.com/kataras/iris/tree/master/_examples/miscellaneous/ratelimit) |
|
||||
|
||||
Community made
|
||||
------------
|
||||
|
|
157
middleware/rate/rate.go
Normal file
157
middleware/rate/rate.go
Normal file
|
@ -0,0 +1,157 @@
|
|||
// TODO: godoc and add tests.
|
||||
package rate
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/kataras/iris/v12/context"
|
||||
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
func init() {
|
||||
context.SetHandlerName("iris/middleware/rate.(*Limiter).serveHTTP-fm", "iris.ratelimit")
|
||||
}
|
||||
|
||||
type Option func(*Limiter)
|
||||
|
||||
func ExceedHandler(handler context.Handler) Option {
|
||||
return func(l *Limiter) {
|
||||
l.exceedHandler = handler
|
||||
}
|
||||
}
|
||||
|
||||
func ClientData(clientDataFunc func(ctx context.Context) interface{}) Option {
|
||||
return func(l *Limiter) {
|
||||
l.clientDataFunc = clientDataFunc
|
||||
}
|
||||
}
|
||||
|
||||
func PurgeEvery(every time.Duration, maxLifetime time.Duration) Option {
|
||||
condition := func(c *Client) bool {
|
||||
// for a custom purger the end-developer may use the c.Data filled from a `ClientData` option.
|
||||
return time.Since(c.LastSeen) > maxLifetime
|
||||
}
|
||||
|
||||
return func(l *Limiter) {
|
||||
go func() {
|
||||
for {
|
||||
time.Sleep(every)
|
||||
|
||||
l.Purge(condition)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
type (
|
||||
Limiter struct {
|
||||
clientDataFunc func(ctx context.Context) interface{} // fill the Client's Data field.
|
||||
exceedHandler context.Handler // when too many requests.
|
||||
|
||||
clients map[string]*Client
|
||||
mu sync.RWMutex // mutex for clients.
|
||||
pool *sync.Pool // object pool for clients.
|
||||
}
|
||||
|
||||
Client struct {
|
||||
limiter *rate.Limiter
|
||||
LastSeen time.Time
|
||||
IP string
|
||||
Data interface{}
|
||||
}
|
||||
)
|
||||
|
||||
// Inf is the infinite rate limit; it allows all events (even if burst is zero).
|
||||
const Inf = math.MaxFloat64
|
||||
|
||||
func Limit(limit float64, burst int, options ...Option) context.Handler {
|
||||
rateLimit := rate.Limit(limit)
|
||||
|
||||
l := &Limiter{
|
||||
clients: make(map[string]*Client),
|
||||
pool: &sync.Pool{New: func() interface{} {
|
||||
return &Client{limiter: rate.NewLimiter(rateLimit, burst)}
|
||||
}},
|
||||
|
||||
exceedHandler: func(ctx context.Context) {
|
||||
ctx.StopWithStatus(429) // Too Many Requests.
|
||||
},
|
||||
}
|
||||
|
||||
for _, opt := range options {
|
||||
opt(l)
|
||||
}
|
||||
|
||||
return l.serveHTTP
|
||||
}
|
||||
|
||||
func (l *Limiter) acquire() *Client {
|
||||
v := l.pool.Get().(*Client)
|
||||
v.LastSeen = time.Now()
|
||||
return v
|
||||
}
|
||||
|
||||
func (l *Limiter) release(client *Client) {
|
||||
client.IP = ""
|
||||
client.Data = nil
|
||||
l.pool.Put(client)
|
||||
}
|
||||
|
||||
func (l *Limiter) Purge(condition func(*Client) bool) {
|
||||
l.mu.Lock()
|
||||
for ip, client := range l.clients {
|
||||
if condition(client) {
|
||||
l.release(client)
|
||||
delete(l.clients, ip)
|
||||
}
|
||||
}
|
||||
l.mu.Unlock()
|
||||
}
|
||||
|
||||
func (l *Limiter) serveHTTP(ctx context.Context) {
|
||||
ip := ctx.RemoteAddr()
|
||||
l.mu.RLock()
|
||||
client, ok := l.clients[ip]
|
||||
l.mu.RUnlock()
|
||||
|
||||
if !ok {
|
||||
client = l.acquire()
|
||||
client.IP = ip
|
||||
|
||||
if l.clientDataFunc != nil {
|
||||
client.Data = l.clientDataFunc(ctx)
|
||||
}
|
||||
// if l.store(ctx, client) {
|
||||
// ^ no, let's keep it simple.
|
||||
l.mu.Lock()
|
||||
l.clients[ip] = client
|
||||
l.mu.Unlock()
|
||||
}
|
||||
|
||||
client.LastSeen = time.Now()
|
||||
ctx.Values().Set(clientContextKey, client)
|
||||
|
||||
if client.limiter.Allow() {
|
||||
ctx.Next()
|
||||
return
|
||||
}
|
||||
|
||||
if l.exceedHandler != nil {
|
||||
l.exceedHandler(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
const clientContextKey = "iris.ratelimit.client"
|
||||
|
||||
func Get(ctx context.Context) *Client {
|
||||
if v := ctx.Values().Get(clientContextKey); v != nil {
|
||||
if c, ok := v.(*Client); ok {
|
||||
return c
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Loading…
Reference in New Issue
Block a user