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
|
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
|
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.
|
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,13 +283,15 @@ You can serve [quicktemplate](https://github.com/valyala/quicktemplate) and [her
|
||||||
|
|
||||||
### Miscellaneous
|
### Miscellaneous
|
||||||
|
|
||||||
|
- [Rate Limit](miscellaneous/ratelimit/main.go) **NEW**
|
||||||
- [HTTP Method Override](https://github.com/kataras/iris/blob/master/middleware/methodoverride/methodoverride_test.go)
|
- [HTTP Method Override](https://github.com/kataras/iris/blob/master/middleware/methodoverride/methodoverride_test.go)
|
||||||
- [Request Logger](http_request/request-logger/main.go)
|
- [Request Logger](http_request/request-logger/main.go)
|
||||||
* [log requests to a file](http_request/request-logger/request-logger-file/main.go)
|
* [log requests to a file](http_request/request-logger/request-logger-file/main.go)
|
||||||
- [Recovery](miscellaneous/recover/main.go)
|
- [Recovery](miscellaneous/recover/main.go)
|
||||||
- [Profiling (pprof)](miscellaneous/pprof/main.go)
|
- [Profiling (pprof)](miscellaneous/pprof/main.go)
|
||||||
- [Internal Application File Logger](miscellaneous/file-logger/main.go)
|
- [Internal Application File Logger](miscellaneous/file-logger/main.go)
|
||||||
- [Google reCAPTCHA](miscellaneous/recaptcha/main.go)
|
- [Google reCAPTCHA](miscellaneous/recaptcha/main.go)
|
||||||
|
- [hCaptcha](miscellaneous/hcaptcha/main.go) **NEW**
|
||||||
|
|
||||||
### Community-based Handlers
|
### 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
|
go.etcd.io/bbolt v1.3.4
|
||||||
golang.org/x/crypto v0.0.0-20200427165652-729f1e841bcc
|
golang.org/x/crypto v0.0.0-20200427165652-729f1e841bcc
|
||||||
golang.org/x/text v0.3.2
|
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/ini.v1 v1.55.0
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
|
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) |
|
| [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) |
|
| [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) |
|
| [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
|
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