پیکربندی محدودکنندههای نرخ
در حال حاضر مقادیر درخواستهای در ثانیه و burst ما به صورت سختافزاری در middleware تابع rateLimit() کدنویسی شدهاند. این خوب است، اما انعطافپذیرتر میشد اگر این مقادیر در زمان اجرا قابل تنظیم بودند. به همین ترتیب، داشتن یک راه آسان برای غیرفعال کردن کامل محدودیت نرخ مفید خواهد بود (برای مثال، ممکن است بخواهید آن را هنگام اجرای بنچمارک یا انجام تست بار غیرفعال کنید و تمام درخواستها از تعداد کمی آدرس IP بیایند).
برای قابل تنظیم کردن این موارد، بیایید به فایل cmd/api/main.go برگردیم و ساختار config و پرچمهای خط فرمان را به این صورت بهروزرسانی کنیم:
package main ... type config struct { port int env string db struct { dsn string maxOpenConns int maxIdleConns int maxIdleTime time.Duration } // Add a new limiter struct containing fields for the requests-per-second and burst // values, and a boolean field which we can use to enable/disable rate limiting // altogether. limiter struct { rps float64 burst int enabled bool } } ... func main() { var cfg config flag.IntVar(&cfg.port, "port", 4000, "API server port") flag.StringVar(&cfg.env, "env", "development", "Environment (development|staging|production)") flag.StringVar(&cfg.db.dsn, "db-dsn", os.Getenv("GREENLIGHT_DB_DSN"), "PostgreSQL DSN") flag.IntVar(&cfg.db.maxOpenConns, "db-max-open-conns", 25, "PostgreSQL max open connections") flag.IntVar(&cfg.db.maxIdleConns, "db-max-idle-conns", 25, "PostgreSQL max idle connections") flag.DurationVar(&cfg.db.maxIdleTime, "db-max-idle-time", 15*time.Minute, "PostgreSQL max connection idle time") // Create command-line flags to read the settings values into the config struct. // Notice that we use true as the default for the 'enabled' setting. flag.Float64Var(&cfg.limiter.rps, "limiter-rps", 2, "Rate limiter maximum requests per second") flag.IntVar(&cfg.limiter.burst, "limiter-burst", 4, "Rate limiter maximum burst") flag.BoolVar(&cfg.limiter.enabled, "limiter-enabled", true, "Enable rate limiter") flag.Parse() ... } ...
و سپس بیایید middleware تابع rateLimit() خود را برای استفاده از این تنظیمات به این صورت بهروزرسانی کنیم:
package main ... func (app *application) rateLimit(next http.Handler) http.Handler { // If rate limiting is not enabled, return the next handler in the chain with // with no further action. if !app.config.limiter.enabled { return next } type client struct { limiter *rate.Limiter lastSeen time.Time } var ( mu sync.Mutex clients = make(map[string]*client) ) go func() { for { time.Sleep(time.Minute) mu.Lock() for ip, client := range clients { if time.Since(client.lastSeen) > 3*time.Minute { delete(clients, ip) } } mu.Unlock() } }() return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ip := realip.FromRequest(r) mu.Lock() if _, found := clients[ip]; !found { clients[ip] = &client{ // Use the requests-per-second and burst values from the config // struct. limiter: rate.NewLimiter(rate.Limit(app.config.limiter.rps), app.config.limiter.burst), } } clients[ip].lastSeen = time.Now() if !clients[ip].limiter.Allow() { mu.Unlock() app.rateLimitExceededResponse(w, r) return } mu.Unlock() next.ServeHTTP(w, r) }) }
پس از اتمام کار، بیایید این را با اجرای API با پرچم -limiter-burst و مقدار burst کاهش یافته به ۲ امتحان کنیم:
$ go run ./cmd/api/ -limiter-burst=2 time=2023-09-10T10:59:13.722+02:00 level=INFO msg="database connection pool established" time=2023-09-10T10:59:13.722+02:00 level=INFO msg="starting server" addr=:4000 env=development
اگر دوباره یک دسته شش درخواست را با سرعت زیاد ارسال کنید، باید اکنون متوجه شوید که فقط دو درخواست اول موفق هستند:
$ for i in {1..6}; do curl http://localhost:4000/v1/healthcheck; done
{
"status": "available",
"system_info": {
"environment": "development",
"version": "1.0.0"
}
}
{
"status": "available",
"system_info": {
"environment": "development",
"version": "1.0.0"
}
}
{
"error": "rate limit exceeded"
}
{
"error": "rate limit exceeded"
}
{
"error": "rate limit exceeded"
}
{
"error": "rate limit exceeded"
}
به همین ترتیب، میتوانید با استفاده از پرچم -limiter-enabled=false محدودکننده نرخ را کلاً غیرفعال کنید:
$ go run ./cmd/api/ -limiter-enabled=false time=2023-09-10T10:59:13.722+02:00 level=INFO msg="database connection pool established" time=2023-09-10T10:59:13.722+02:00 level=INFO msg="starting server" addr=:4000 env=development
و باید متوجه شوید که اکنون تمام درخواستها با موفقیت تکمیل میشوند، هر تعداد که ارسال کنید.
$ for i in {1..6}; do curl http://localhost:4000/v1/healthcheck; done
{
"status": "available",
"system_info": {
"environment": "development",
"version": "1.0.0"
}
}
...
{
"status": "available",
"system_info": {
"environment": "development",
"version": "1.0.0"
}
}