使用Gorilla Mux的速率限制器

huangapple go评论76阅读模式
英文:

Rate limiter with gorilla mux

问题

我正在尝试实现HTTP请求限制器,以允许每个用户每秒发送10个请求,根据他们的用户名进行限制。
最多可以向服务器发送10个请求,包括正在处理中的请求。
以下是我根据rate-limit的参考实现的代码。

func init() {
	go cleanupVisitors()
}

func getVisitor(username string) *rate.Limiter {
	mu.Lock()
	defer mu.Unlock()
	v, exists := visitors[username]
	if !exists {
		limiter := rate.NewLimiter(10, 3)
		visitors[username] = &visitor{limiter, time.Now()}
		return limiter
	}
	v.lastSeen = time.Now()
	return v.limiter
}

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.ProcessHello).Methods("GET")
	ws := r.PathPrefix("/index.php").HandlerFunc(api.ProcessWs).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, handlers.CompressHandler(r))
		if err != nil {
			fmt.Println(err)
			log.Println(err)
		}
	}
}

我有几个疑问。

我只想对/index.php进行限制,而不是对/hello进行限制。我使用了子路由来实现,这样做正确吗?

限制中间件没有按照我预期的那样进行限制。它只允许一个成功的请求,而其他请求都返回了太多请求的错误。

我在这里漏掉了什么?

英文:

I am trying to implement http request limiter to allow 10 request per second per user by their usernames.
At the max 10 request can be hit to the server including requests which are under processing.
Below is what I have implemented with reference of rate-limit.

func init() {
go cleanupVisitors()
}
func getVisitor(username string) *rate.Limiter {
mu.Lock()
defer mu.Unlock()
v, exists := visitors[username]
if !exists {
limiter := rate.NewLimiter(10, 3)
visitors[username] = &visitor{limiter, time.Now()}
return limiter
}
v.lastSeen = time.Now()
return v.limiter
}
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.ProcessHello).Methods("GET")
ws := r.PathPrefix("/index.php").HandlerFunc(api.ProcessWs).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, 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 Sub route. Is it correct way?

The limit middle ware is not limiting as I assumed. It allows 1 successful request all other requests are returned with too many requests error.

What am I missing here. ?

答案1

得分: 1

子路由模式是Gorilla提出的一种解决方案,虽然只是一个小的组织建议:

r := mux.NewRouter()
r.HandlerFunc("/hello", api.ProcessHello).Methods("GET")
r.HandleFunc("/favicon.ico", faviconHandler)
r.Use(panicRecovery)

ws := r.PathPrefix("/index.php").Subrouter()
ws.Use(limit)
ws.HandlerFunc(api.ProcessWs).Methods("GET", "POST")

你似乎不仅通过Use()方法调用中间件,还通过ListenAndServe的处理程序调用它。我还从Gorilla的相同示例中看到了一个更清晰的方法:

server := &http.Server{
    Addr:         "0.0.0.0:8080",
    // 设置超时时间以避免Slowloris攻击。
    WriteTimeout: time.Second * 15,
    ReadTimeout:  time.Second * 15,
    IdleTimeout:  time.Second * 60,
    Handler:      router, // 传入我们的gorilla/mux实例。
}

fmt.Println("starting server")
if err := server.ListenAndServe(); err != nil {
    fmt.Println(err)
}

此外,根据你的源代码,你实现的速率限制模式是按用户限制请求次数,但你使用的是用户名而不是IP地址来限制请求。你的问题开始时没有明确说明你是想按用户限制请求次数还是限制整个端点的请求次数,所以也许你会遇到意外的行为。

英文:

the subrouter pattern is a solution gorilla proposes , small organizational suggestion though:

    r := mux.NewRouter()
r.HandlerFunc("/hello", api.ProcessHello).Methods("GET")
r.HandleFunc("/favicon.ico", faviconHandler)
r.Use(panicRecovery)
ws := r.PathPrefix("/index.php").Subrouter()
ws.Use(limit)
ws.HandlerFunc(api.ProcessWs).Methods("GET", "POST")

you seem to be calling your middleware not only via the Use() method but also calling it over the handler on ListenAndServe, I also see from gorilla same example that a more clear way to approach this is:

  server := &http.Server{
Addr:         "0.0.0.0:8080",
// Good practice to set timeouts to avoid Slowloris attacks.
WriteTimeout: time.Second * 15,
ReadTimeout:  time.Second * 15,
IdleTimeout:  time.Second * 60,
Handler: router, // Pass our instance of gorilla/mux in.
}
fmt.Println("starting server")
if err := server.ListenAndServe(); err != nil {
fmt.Println(err)
}

Also, from your source, the pattern of rate limiting you are implementing is to rate limit per user, but you use usernames instead of their IPs to limit their requests, and your question begins without clarifying if you wish to ratelimit per user or rate limit how many requests can be done to the endpoint overall - so maybe you might be getting unexpected behavior due to that too.

huangapple
  • 本文由 发表于 2021年9月30日 13:15:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/69386505.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定