Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adds middleware for rate limiting #1724

Merged
merged 40 commits into from
Jan 15, 2021

Conversation

iambenkay
Copy link
Contributor

@iambenkay iambenkay commented Dec 17, 2020

What's New?

This feature implements a store agnostic rate limiting middleware i.e configurable with any store of user's choice.

Here is a dummy snippet of how a certain (unconventional) redis store might be integrated with the rate limiting middleware

type RedisStore struct {
   client redis.Client
}

// Store config must implement Allow for rate limiting middleware
func (store *RedisStore) Allow(identifier) bool {
   // run logic here that decides if user should be permitted
   return true
}

func main(){
   e := echo.New()

   redisStore := RedisStore{
      client: redis.Client{}
   }
   
   limiterMW := middleware.RateLimiter(redisStore)
   e.Use(limiterMW)
}

I threw in an InMemory implementation for people like me who want to get on the go fast.

func main(){
   e := echo.New()

   var inMemoryStore = middleware.RateLimiterMemoryStore{
      rate: 1,
      burst: 3,
   }
   
   limiterMW := middleware.RateLimiterWithConfig(RateLimiterConfig{
      Store: &inMemoryStore,
      SourceFunc: func(ctx echo.Context) string {
         return ctx.RealIP()
      },
   })
   e.Use(limiterMW)
}

closes #1721

@codecov
Copy link

codecov bot commented Dec 17, 2020

Codecov Report

Merging #1724 (bbfb0ab) into master (67263b5) will increase coverage by 0.44%.
The diff coverage is 100.00%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1724      +/-   ##
==========================================
+ Coverage   86.88%   87.32%   +0.44%     
==========================================
  Files          30       31       +1     
  Lines        2089     2162      +73     
==========================================
+ Hits         1815     1888      +73     
  Misses        175      175              
  Partials       99       99              
Impacted Files Coverage Δ
middleware/rate_limiter.go 100.00% <100.00%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 67263b5...bbfb0ab. Read the comment docs.

@iambenkay iambenkay force-pushed the feature/rate-limiter-middleware branch from a945c80 to 9b63f99 Compare December 17, 2020 12:04
@iambenkay
Copy link
Contributor Author

iambenkay commented Dec 17, 2020

Using time.Sleep in tests produces different results on different machines due to different computation speeds on different machines for example the github action ran the tests sucessfully on ubuntu but failed on macos before I had to take it out.

TODO: Find a more reliable method for testing the rate limiting which does not involve using the time.Sleep

I am open to any suggestions on this time issue.

middleware/rate_limiter.go Outdated Show resolved Hide resolved
middleware/rate_limiter.go Outdated Show resolved Hide resolved
middleware/rate_limiter.go Outdated Show resolved Hide resolved
@lammel
Copy link
Contributor

lammel commented Dec 17, 2020

Lovely overall!
I'm looking forward to put that beast into action. As we are using freecache currently I'll probably implement something like a FreeCacheLimiterStore for that then. Good to know that's easily doable.

@iambenkay
Copy link
Contributor Author

TODO: Implement an expire functionality in order to automatically manage the amount of visitors that are currently active.

Added a last seen property to the visitor struct. I intend on adding an Optional AutoExpire method to the default InMemoryStore eventually.

@iambenkay iambenkay force-pushed the feature/rate-limiter-middleware branch from c6e7fc6 to 8d34f11 Compare December 17, 2020 17:31
middleware/rate_limiter.go Outdated Show resolved Hide resolved
Copy link
Contributor

@lammel lammel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks even better now...

@iambenkay
Copy link
Contributor Author

Now Custom Error Handler can be specified using RateLimiterWithConfig

mw := middleware.RateLimiterWithConfig(RateLimiterConfig{
   ErrorHandler: func(c echo.Context) error {
      return c.JSON(http.StatusTooManyRequests, nil)
   },
   Store: inMemoryStore,
})

Copy link
Contributor

@lammel lammel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pafuent you missed again all the fun. Feedback welcome, ready to merge.

@iambenkay Good work. Can you prepare a PR for the docs too in echox?

@lammel lammel added this to In progress in Echo v4.2 Dec 17, 2020
Copy link
Contributor

@pafuent pafuent left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@iambenkay I like the overall implementation. Please let me know if my comments makes sense for you.

@lammel Yep, it seems that I missed the fun 😢 (it was hard to resist the temptation to stop working and have some fun when I saw the emails back and forth)

middleware/rate_limiter.go Outdated Show resolved Hide resolved
middleware/rate_limiter.go Outdated Show resolved Hide resolved
middleware/rate_limiter.go Outdated Show resolved Hide resolved
middleware/rate_limiter_test.go Outdated Show resolved Hide resolved
middleware/rate_limiter_test.go Outdated Show resolved Hide resolved
middleware/rate_limiter.go Outdated Show resolved Hide resolved
@pafuent
Copy link
Contributor

pafuent commented Jan 6, 2021

@iambenkay please address this two comments:
#1724 (comment)
#1724 (comment)
Besides of those two LGTM, so after addressing those I'll approve the PR. Thanks for the hard work and patience.

Copy link
Contributor

@lammel lammel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice. We should just discuss the Allow signature.

// RateLimiterStore is the interface to be implemented by custom stores.
RateLimiterStore interface {
// Stores for the rate limiter have to implement the Allow method
Allow(identifier string) bool
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Allow may have internal errors due to hash limits or other issues.
In this case it may make sense to make the signature to be Allow(identifier string) bool, error to be able to handle this special cases as we will be able to pass the error also to the DenyHandler. Use case would be for example to allow access (not apply rate limit) for DB connection errors (or other internal errors).

Not sure if this is needed, but it was also suggested by @aldas

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay let me include this. it is a valid point.

@lammel
Copy link
Contributor

lammel commented Jan 6, 2021

@iambenkay Pushed some comment improvements to resolve some remaining comments.

@lammel
Copy link
Contributor

lammel commented Jan 6, 2021

@iambenkay Can you do a documentation PR in labstack/echox too?

@iambenkay
Copy link
Contributor Author

Yes!

Copy link
Contributor

@lammel lammel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pafuent Approved from my side. Feel free to merge.
Thanks (especially for your patience) @iambenkay.

Btw. tested the limiter locally with a simple hello world and it worked as expected with up to 30000 req/s and my laptop (from single local IP)

@iambenkay
Copy link
Contributor Author

That's super!

@iambenkay
Copy link
Contributor Author

@pafuent calling your attention to this!

Copy link
Contributor

@pafuent pafuent left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@iambenkay Thanks for this new middleware and thanks for your patience

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
No open projects
Development

Successfully merging this pull request may close these issues.

Rate Limiting for Echo using golang.org/x/time/rate
4 participants