Files
link-shortener/internal/middleware/ratelimit.go
Giovanni Rezcjikov 8befcc11c1
Some checks failed
continuous-integration/drone/push Build is failing
feat: increased security
2026-02-21 21:05:47 +03:00

75 lines
1.3 KiB
Go

package middleware
import (
"net/http"
"sync"
"time"
"github.com/gin-gonic/gin"
"golang.org/x/time/rate"
)
type client struct {
limiter *rate.Limiter
lastSeen time.Time
}
type RateLimiter struct {
clients map[string]*client
mu sync.Mutex
rps rate.Limit
burst int
}
func NewRateLimiter(requestsPerSecond float64, burst int) *RateLimiter {
rl := &RateLimiter{
clients: make(map[string]*client),
rps: rate.Limit(requestsPerSecond),
burst: burst,
}
go rl.cleanup()
return rl
}
func (rl *RateLimiter) getClient(ip string) *rate.Limiter {
rl.mu.Lock()
defer rl.mu.Unlock()
if c, exists := rl.clients[ip]; exists {
c.lastSeen = time.Now()
return c.limiter
}
limiter := rate.NewLimiter(rl.rps, rl.burst)
rl.clients[ip] = &client{limiter: limiter, lastSeen: time.Now()}
return limiter
}
func (rl *RateLimiter) cleanup() {
for {
time.Sleep(time.Minute)
rl.mu.Lock()
for ip, c := range rl.clients {
if time.Since(c.lastSeen) > 3*time.Minute {
delete(rl.clients, ip)
}
}
rl.mu.Unlock()
}
}
func (rl *RateLimiter) Middleware() gin.HandlerFunc {
return func(c *gin.Context) {
ip := c.ClientIP()
limiter := rl.getClient(ip)
if !limiter.Allow() {
c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{
"error": "failed",
"message": "rate limit exceeded",
})
return
}
c.Next()
}
}