3

我正在尝试实现 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 个成功的请求,所有其他请求都返回太多请求错误。

我在这里想念什么。?

4

1 回答 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",
        // 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 来限制他们的请求,并且您的问题开始时没有澄清您是否希望对每个用户进行速率限制或速率限制总体上可以对端点执行多少请求-因此,您可能也会因此而遇到意外行为。

于 2021-10-01T13:32:20.730 回答