ch11
This commit is contained in:
@@ -51,3 +51,8 @@ func (app *application) editConflictResponse(w http.ResponseWriter, r *http.Requ
|
||||
message := "unable to update the record due to an edit conflict, please try again"
|
||||
app.errorResponse(w, r, http.StatusConflict, message)
|
||||
}
|
||||
|
||||
func (app *application) rateLimitExceededResponse(w http.ResponseWriter, r *http.Request) {
|
||||
message := "rate limit exceeded"
|
||||
app.errorResponse(w, r, http.StatusTooManyRequests, message)
|
||||
}
|
||||
|
||||
@@ -32,6 +32,11 @@ type config struct {
|
||||
maxIdleConns int
|
||||
maxIdleTime string
|
||||
}
|
||||
limiter struct {
|
||||
rps float64
|
||||
burst int
|
||||
enabled bool
|
||||
}
|
||||
}
|
||||
|
||||
func openDB(cfg config) (*sql.DB, error) {
|
||||
@@ -69,6 +74,11 @@ func main() {
|
||||
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.StringVar(&cfg.db.maxIdleTime, "db-max-idle-time", "15m", "PostgreSQL max connection idle time")
|
||||
|
||||
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()
|
||||
|
||||
logger := jsonlog.New(os.Stdout, jsonlog.LevelInfo)
|
||||
|
||||
@@ -2,7 +2,12 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
func (app *application) recoverPanic(next http.Handler) http.Handler {
|
||||
@@ -16,3 +21,57 @@ func (app *application) recoverPanic(next http.Handler) http.Handler {
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func (app *application) rateLimit(next http.Handler) http.Handler {
|
||||
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) {
|
||||
if app.config.limiter.enabled {
|
||||
ip, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||
if err != nil {
|
||||
app.serverErrorResponse(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
|
||||
if _, found := clients[ip]; !found {
|
||||
clients[ip] = &client{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)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -20,5 +20,5 @@ func (app *application) routes() http.Handler {
|
||||
router.HandlerFunc(http.MethodPatch, "/v1/movies/:id", app.updateMovieHandler)
|
||||
router.HandlerFunc(http.MethodDelete, "/v1/movies/:id", app.deleteMovieHandler)
|
||||
|
||||
return app.recoverPanic(router)
|
||||
return app.recoverPanic(app.rateLimit(router))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user