I am trying to implement http request limiter to allow 10 request per second.
Below is what I have implemented with reference of rate-limit.
// Run a background goroutine to remove old entries from the visitors map.
func init() {
fmt.Println("This will get called on main initialization")
go cleanupVisitors()
}
func getVisitor(username string) *rate.Limiter {
mu.Lock()
defer mu.Unlock()
v, exists := visitors[username]
if !exists {
//rt := rate.Every(1 * time.Minute)
//limiter := rate.NewLimiter(rt, 1)
limiter := rate.NewLimiter(5, 3)
// Include the current time when creating a new visitor.
visitors[username] = &visitor{limiter, time.Now()}
return limiter
}
// Update the last seen time for the visitor.
v.lastSeen = time.Now()
return v.limiter
}
// Every minute check the map for visitors that haven't been seen for
// more than 3 minutes and delete the entries.
func cleanupVisitors() {
for {
time.Sleep(time.Minute)
mu.Lock()
for username, v := range visitors {
if time.Since(v.lastSeen) > 1*time.Minute {
delete(visitors, username)
}
}
mu.Unlock()
}
}
func limit(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
mappedArray := hotelapi.SearchResponse{}
mappedArray.StartTime = time.Now().Format("2006-02-01 15:04:05.000000")
mappedArray.EndTime = time.Now().Format("2006-02-01 15:04:05.000000")
userName := r.FormValue("username")
limiter := getVisitor(userName)
if !limiter.Allow() {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusTooManyRequests)
mappedArray.MessageInfo = http.StatusText(http.StatusTooManyRequests)
mappedArray.ErrorCode = strconv.Itoa(http.StatusTooManyRequests)
json.NewEncoder(w).Encode(mappedArray)
return
}
next.ServeHTTP(w, r)
})
}
func route() {
r := mux.NewRouter()
r.PathPrefix("/hello").HandlerFunc(api.ProcessSupplier).Methods("GET")
ws := r.PathPrefix("/index.php").HandlerFunc(api.ProcessWebService).Methods("GET", "POST").Subrouter()
r.Use(panicRecovery)
ws.Use(limit)
http.HandleFunc("/favicon.ico", faviconHandler)
if config.HTTPSEnabled {
err := http.ListenAndServeTLS(":"+config.Port, config.HTTPSCertificateFilePath, config.HTTPSKeyFilePath, handlers.CompressHandlerLevel(r, gzip.BestSpeed))
if err != nil {
fmt.Println(err)
log.Println(err)
}
} else {
err := http.ListenAndServe(":"+config.Port, limit(handlers.CompressHandler(r)))
if err != nil {
fmt.Println(err)
log.Println(err)
}
}
}
I have couple of concerns here.
I want limiter only for /index.php and not for /hello. I did implement with Subroute. Is it correct way?
The limit middleware is not limiting as I assumed. It allows 1 successful request all other requests are returned with too many requests error.
Do you mean it returns too many requests even within the first 10? If so, please give us the full setup of the router. You may also try just simple one:
ws.Use(httprate.LimitByIP(3, 5*time.Second))
Instead of the one above. And for the one above, try to set 100 requests per 10 seconds, it maybe makes some difference.
Hello, Thanks . I did changes and its working . I am making more tests.
Just one concern, how can I make limit and duration dynamic for each user ?
I am trying to access username with r.FormValue("username") but since httprate.Limit doesn’t have r *http.Request I am getting blank username.
Well, you have to use the first method that didn’t work well and debug what’s wrong or eventually report an issue to go-chi/httprate. Also check the examples and tests there.
Sorry. Forgot to mention. I tried with LimitByIP and accordingly debug and corrected the code which I was trying in first method. Now first method also working as expected. Now I am just struggling to make limit dynamic based on username field in URL. I checked the examples but couldn’t find any. Trying more. If not found will raise with go-chi/httprate.
Thank you.
I did this. For testing purpose I passed token as a random text (not actual username) and still strangely limiter was applied on it.
My actual username is : ganesh in r.FormValue(“username”).