Configuring the Rate Limiters.

This commit is contained in:
Maxime Delporte
2025-11-18 20:10:55 +01:00
parent 7de9ef89b7
commit 83bb7294db
2 changed files with 42 additions and 25 deletions

View File

@@ -28,17 +28,23 @@ For now, the only configuration settings will be the network port that we want t
and the name of the current operating environment for the application (development, staging, production, etc.) and the name of the current operating environment for the application (development, staging, production, etc.)
We'll read in the configuration settings from command-line flags when the application starts. We'll read in the configuration settings from command-line flags when the application starts.
*/ */
// Add a db struct field to hold the configuration settings for our database connection pool. For now, this only holds the DSN, which we will read in from a command-line flag.
// Add maxOpenConns, maxIdleConns and maxIdleTime fields to hold the configuration settings for the connection pool
type config struct { type config struct {
port int port int
env string env string
// Add a db struct field to hold the configuration settings for our database connection pool. For now, this only holds the DSN, which we will read in from a command-line flag.
db struct { db struct {
dsn string dsn string
// Add maxOpenConns, maxIdleConns and maxIdleTime fields to hold the configuration settings for the connection pool
maxOpenConns int maxOpenConns int
maxIdleConns int maxIdleConns int
maxIdleTime time.Duration 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
}
} }
// application struct hold the dependencies for our HTTP handlers, helpers, and middleware. // application struct hold the dependencies for our HTTP handlers, helpers, and middleware.
@@ -68,6 +74,11 @@ func main() {
flag.IntVar(&cfg.db.maxIdleConns, "db-max-idle-conns", 25, "PostgreSQL max idle 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") flag.DurationVar(&cfg.db.maxIdleTime, "db-max-idle-time", 15*time.Minute, "PostgreSQL max connection idle time")
// Create command line flags to read the setting values into the config struct. Notice taht 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() flag.Parse()
// Initialize a new structured logger which writes log entries to standard out stream. // Initialize a new structured logger which writes log entries to standard out stream.

View File

@@ -62,6 +62,8 @@ func (app *application) rateLimit(next http.Handler) http.Handler {
// The function we are returning is a closure, which 'closes over' the limiter variable // The function we are returning is a closure, which 'closes over' the limiter variable
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Only carry out the check if rate limiting is enabled
if app.config.limiter.enabled {
// Extract the client's IP address from request. // Extract the client's IP address from request.
ip, _, err := net.SplitHostPort(r.RemoteAddr) ip, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil { if err != nil {
@@ -72,9 +74,12 @@ func (app *application) rateLimit(next http.Handler) http.Handler {
// Lock the mutex to prevent this code from being executed concurrently // Lock the mutex to prevent this code from being executed concurrently
mu.Lock() mu.Lock()
// Check to see if the IP address already exists in the map. If it doesn't, then initialize a new rate limiter and add the IP address and limiter to he map. // Check to see if the IP address already exists in the map. If it doesn't, then initialize a new rate limiter and add the IP address and limiter to the map.
if _, found := clients[ip]; !found { if _, found := clients[ip]; !found {
clients[ip] = &client{limiter: rate.NewLimiter(2, 4)} // Use the requests-per-second and burst values from the config struct
rps := rate.Limit(app.config.limiter.rps)
burst := app.config.limiter.burst
clients[ip] = &client{limiter: rate.NewLimiter(rps, burst)}
} }
// Update the last see time for the client. // Update the last see time for the client.
@@ -89,6 +94,7 @@ func (app *application) rateLimit(next http.Handler) http.Handler {
// Very importantly, unlock the mutex before calling the next handler in the chain. Notice that we DON'T use defer to unlock the mutex, as that would mean that the mutex isn't unlocked until all the handlers downstream of this middleware have also returned // Very importantly, unlock the mutex before calling the next handler in the chain. Notice that we DON'T use defer to unlock the mutex, as that would mean that the mutex isn't unlocked until all the handlers downstream of this middleware have also returned
mu.Unlock() mu.Unlock()
}
next.ServeHTTP(w, r) next.ServeHTTP(w, r)
}) })