【问题标题】:Rate limiter with gorilla mux带有大猩猩多路复用器的速率限制器
【发布时间】:2021-11-21 22:59:43
【问题描述】:

我正在尝试实施 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。我确实使用子路由实现了。方法对吗?

限制中间件并不像我想象的那样限制。它允许 1 个成功的请求,所有其他请求都返回太多请求错误。

我在这里错过了什么。 ?

【问题讨论】:

  • 您确定userName := r.FormValue("username") 会为每个请求获取有效的非空白值吗?如果不是,那么所有请求将只使用一个速率限制器。
  • @colm.anseo 是的。我收到非空白用户名。

标签: go rate-limiting gorilla mux go-map


【解决方案1】:

子路由器模式is a solution gorilla proposes,不过是小组织建议:

    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",
        // 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)
  }

此外,从您的消息来源来看,您正在实施的速率限制模式是对每个用户进行速率限制,但是您使用用户名而不是他们的 IP 来限制他们的请求,并且您的问题开始时没有说明您是否希望对每个用户进行速率限制或速率限制可以对端点执行的请求总数 - 因此,您可能也会因此而遇到意外行为。

【讨论】:

  • 您好,谢谢您的回复。我已经更新了这个问题。是的,我两次错误地使用了中间件。
  • 问题还在发生吗?
  • 是的:(我不知道哪里错了。
  • 您是否尝试根据我的建议更新代码?我也看不到 config 变量来自哪里(这可能只是由于 sn-p 不完整);您可以发布代码的更新版本吗?
猜你喜欢
  • 2016-05-12
  • 2018-12-01
  • 2015-01-11
  • 2021-04-08
  • 2018-06-08
  • 2014-03-15
  • 2018-09-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多