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

Rate limit only on failed requests. #24

Open
chance-schultz opened this issue Apr 8, 2024 · 3 comments
Open

Rate limit only on failed requests. #24

chance-schultz opened this issue Apr 8, 2024 · 3 comments

Comments

@chance-schultz
Copy link

Is there an easy way to use the existing httprate functionality but push it to the end resolution of the request/response chain instead of at the beginning.

We want to create a couple of open ended mutation endpoints that could be used maliciously so we want to limit the total number of bad requests in a short period of time, but not limit penalize a user who is submitted good data that returns 2XX responses.

@VojtechVitek
Copy link
Contributor

Hi @chance-schultz,

This is an interesting feature request!

The current httprate pkg does rate-limiting only at the beginning of the request, which makes sense.

However I can imagine you could write a custom middleware that manipulates the limits after the handler is run and HTTP status is known.

Pseudo-code (not tested / might not compile):

rateLimiter := httprate.NewRateLimiter(1000, time.Minute, httprate.WithKeyFuncs(httprate.KeyByIP))

r := chi.NewRouter()

r.Use(rateLimiter.Handler)

r.Use(func(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor)
		next.ServeHTTP(ww, r)
		
		if ww.Status() >= 200 && ww.Status < 400 {
			key := httprate.KeyByRealIP(r)
			currentWindow := time.Now().UTC().Truncate(time.Minute)
			rateLimiter.Counter().IncrementBy(key, currentWindow, -1) // Put back 
		}
	})
})

@kamilzzz
Copy link

@VojtechVitek in your example imagine you set rate limiter to 5 requests per 1 minute.
The idea is to rate limit if number of bad requests exceed 5.
The side effect of suggested approach is that when user issues 6 simultaneous requests one of them is going to be rate limited as you only manipulate counter after the request finishes.

@VojtechVitek
Copy link
Contributor

@kamilzzz Yes, you're right. You'd need to explicitly check and increment the HTTP 400 limits in a custom middleware. The package API is not really prepared for this use case right now. You'd need to adjust some of the https://github.com/go-chi/httprate/blob/master/limiter.go#L71 logic.

Pseudo code:

rateLimiter := httprate.NewRateLimiter(5, time.Minute, httprate.WithKeyFuncs(httprate.KeyByIP))

r := chi.NewRouter()
  
r.Use(func(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		key := httprate.KeyByRealIP(r)
		currentWindow := time.Now().UTC().Truncate(time.Minute)
		previousWindow := currentWindow.Add(-time.Minute)
		
		prev, next, _ := rateLimiter.Counter().Get(key, currentWindow, previousWindow)
		// check if rate-limited and return HTTP 429
	
		ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor)
		next.ServeHTTP(ww, r)
		
		if ww.Status() == 400 {
			rateLimiter.Counter().IncrementBy(key, currentWindow, 1) // Rate-limit requests with wrong payload
		}
	})
})

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants