ax(mining): rename rl receiver to limiter in RateLimiter methods
AX Principle 1 — predictable names over short names. `rl` requires mental decoding; `limiter` is self-documenting at the call site. Co-Authored-By: Charon <charon@lethean.io>
This commit is contained in:
parent
f70f477745
commit
7e66f32ee5
1 changed files with 40 additions and 40 deletions
|
|
@ -8,8 +8,8 @@ import (
|
|||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// rl := NewRateLimiter(10, 20) // 10 req/s, burst 20
|
||||
// router.Use(rl.Middleware())
|
||||
// limiter := NewRateLimiter(10, 20) // 10 req/s, burst 20
|
||||
// router.Use(limiter.Middleware())
|
||||
type RateLimiter struct {
|
||||
requestsPerSecond int
|
||||
burst int
|
||||
|
|
@ -24,10 +24,10 @@ type rateLimitClient struct {
|
|||
lastCheck time.Time
|
||||
}
|
||||
|
||||
// rl := NewRateLimiter(10, 20) // 10 requests/second, burst of 20
|
||||
// defer rl.Stop()
|
||||
// limiter := NewRateLimiter(10, 20) // 10 requests/second, burst of 20
|
||||
// defer limiter.Stop()
|
||||
func NewRateLimiter(requestsPerSecond, burst int) *RateLimiter {
|
||||
rl := &RateLimiter{
|
||||
limiter := &RateLimiter{
|
||||
requestsPerSecond: requestsPerSecond,
|
||||
burst: burst,
|
||||
clients: make(map[string]*rateLimitClient),
|
||||
|
|
@ -35,72 +35,72 @@ func NewRateLimiter(requestsPerSecond, burst int) *RateLimiter {
|
|||
}
|
||||
|
||||
// Start cleanup goroutine
|
||||
go rl.cleanupLoop()
|
||||
go limiter.cleanupLoop()
|
||||
|
||||
return rl
|
||||
return limiter
|
||||
}
|
||||
|
||||
// go rl.cleanupLoop() // started by NewRateLimiter; runs until rl.Stop()
|
||||
func (rl *RateLimiter) cleanupLoop() {
|
||||
// go limiter.cleanupLoop() // started by NewRateLimiter; runs until limiter.Stop()
|
||||
func (limiter *RateLimiter) cleanupLoop() {
|
||||
ticker := time.NewTicker(time.Minute)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-rl.stopChan:
|
||||
case <-limiter.stopChan:
|
||||
return
|
||||
case <-ticker.C:
|
||||
rl.cleanup()
|
||||
limiter.cleanup()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// rl.cleanup() // called every minute by cleanupLoop; evicts IPs idle for >5 minutes
|
||||
func (rl *RateLimiter) cleanup() {
|
||||
rl.mutex.Lock()
|
||||
defer rl.mutex.Unlock()
|
||||
// limiter.cleanup() // called every minute by cleanupLoop; evicts IPs idle for >5 minutes
|
||||
func (limiter *RateLimiter) cleanup() {
|
||||
limiter.mutex.Lock()
|
||||
defer limiter.mutex.Unlock()
|
||||
|
||||
for ip, client := range rl.clients {
|
||||
for ip, client := range limiter.clients {
|
||||
if time.Since(client.lastCheck) > 5*time.Minute {
|
||||
delete(rl.clients, ip)
|
||||
delete(limiter.clients, ip)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// rl.Stop() // call on shutdown to release the cleanup goroutine
|
||||
func (rl *RateLimiter) Stop() {
|
||||
rl.mutex.Lock()
|
||||
defer rl.mutex.Unlock()
|
||||
// limiter.Stop() // call on shutdown to release the cleanup goroutine
|
||||
func (limiter *RateLimiter) Stop() {
|
||||
limiter.mutex.Lock()
|
||||
defer limiter.mutex.Unlock()
|
||||
|
||||
if !rl.stopped {
|
||||
close(rl.stopChan)
|
||||
rl.stopped = true
|
||||
if !limiter.stopped {
|
||||
close(limiter.stopChan)
|
||||
limiter.stopped = true
|
||||
}
|
||||
}
|
||||
|
||||
// router.Use(rl.Middleware()) // install before route handlers
|
||||
func (rl *RateLimiter) Middleware() gin.HandlerFunc {
|
||||
// router.Use(limiter.Middleware()) // install before route handlers
|
||||
func (limiter *RateLimiter) Middleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
ip := c.ClientIP()
|
||||
|
||||
rl.mutex.Lock()
|
||||
client, exists := rl.clients[ip]
|
||||
limiter.mutex.Lock()
|
||||
client, exists := limiter.clients[ip]
|
||||
if !exists {
|
||||
client = &rateLimitClient{tokens: float64(rl.burst), lastCheck: time.Now()}
|
||||
rl.clients[ip] = client
|
||||
client = &rateLimitClient{tokens: float64(limiter.burst), lastCheck: time.Now()}
|
||||
limiter.clients[ip] = client
|
||||
}
|
||||
|
||||
// Token bucket algorithm
|
||||
now := time.Now()
|
||||
elapsed := now.Sub(client.lastCheck).Seconds()
|
||||
client.tokens += elapsed * float64(rl.requestsPerSecond)
|
||||
if client.tokens > float64(rl.burst) {
|
||||
client.tokens = float64(rl.burst)
|
||||
client.tokens += elapsed * float64(limiter.requestsPerSecond)
|
||||
if client.tokens > float64(limiter.burst) {
|
||||
client.tokens = float64(limiter.burst)
|
||||
}
|
||||
client.lastCheck = now
|
||||
|
||||
if client.tokens < 1 {
|
||||
rl.mutex.Unlock()
|
||||
limiter.mutex.Unlock()
|
||||
respondWithError(c, http.StatusTooManyRequests, "RATE_LIMITED",
|
||||
"too many requests", "rate limit exceeded")
|
||||
c.Abort()
|
||||
|
|
@ -108,14 +108,14 @@ func (rl *RateLimiter) Middleware() gin.HandlerFunc {
|
|||
}
|
||||
|
||||
client.tokens--
|
||||
rl.mutex.Unlock()
|
||||
limiter.mutex.Unlock()
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// if rl.ClientCount() == 0 { /* no active clients */ }
|
||||
func (rl *RateLimiter) ClientCount() int {
|
||||
rl.mutex.RLock()
|
||||
defer rl.mutex.RUnlock()
|
||||
return len(rl.clients)
|
||||
// if limiter.ClientCount() == 0 { /* no active clients */ }
|
||||
func (limiter *RateLimiter) ClientCount() int {
|
||||
limiter.mutex.RLock()
|
||||
defer limiter.mutex.RUnlock()
|
||||
return len(limiter.clients)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue