diff --git a/internal/autoscaling/autoscaler.go b/internal/autoscaling/autoscaler.go deleted file mode 100644 index a44e201..0000000 --- a/internal/autoscaling/autoscaler.go +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright 2026 Safronov Grigorii - * - * Licensed under the CDDL, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * - * You may obtain a copy of the License at - * https://opensource.org/licenses/CDDL-1.0 - */ - -// Файл: internal/api/rate_limiter.go -// Назначение: Rate limiting для API - -package api - -import ( - "fmt" - "net/http" - "sync" - "sync/atomic" - "time" -) - -// RateLimiterConfig содержит конфигурацию rate limiter'а -type RateLimiterConfig struct { - RequestsPerSecond int - BurstSize int - CleanupInterval time.Duration - MaxEntries int -} - -// DefaultRateLimiterConfig возвращает конфигурацию по умолчанию -func DefaultRateLimiterConfig() *RateLimiterConfig { - return &RateLimiterConfig{ - RequestsPerSecond: 100, - BurstSize: 20, - CleanupInterval: 5 * time.Minute, - MaxEntries: 10000, - } -} - -// TokenBucket реализует алгоритм token bucket -type TokenBucket struct { - tokens float64 - maxTokens float64 - refillRate float64 - lastRefill time.Time - mu sync.Mutex -} - -// NewTokenBucket создаёт новый token bucket -func NewTokenBucket(ratePerSecond float64, burst int) *TokenBucket { - return &TokenBucket{ - tokens: float64(burst), - maxTokens: float64(burst), - refillRate: ratePerSecond, - lastRefill: time.Now(), - } -} - -// Allow проверяет, разрешён ли запрос -func (tb *TokenBucket) Allow() bool { - tb.mu.Lock() - defer tb.mu.Unlock() - - now := time.Now() - elapsed := now.Sub(tb.lastRefill).Seconds() - tb.tokens += elapsed * tb.refillRate - if tb.tokens > tb.maxTokens { - tb.tokens = tb.maxTokens - } - tb.lastRefill = now - - if tb.tokens >= 1.0 { - tb.tokens -= 1.0 - return true - } - return false -} - -// RateLimiter управляет rate limiting для API -type RateLimiter struct { - config *RateLimiterConfig - limiters sync.Map - rejected atomic.Uint64 - allowed atomic.Uint64 - stopChan chan struct{} - wg sync.WaitGroup -} - -// NewRateLimiter создаёт новый rate limiter -func NewRateLimiter(config *RateLimiterConfig) *RateLimiter { - if config == nil { - config = DefaultRateLimiterConfig() - } - - rl := &RateLimiter{ - config: config, - stopChan: make(chan struct{}), - } - - rl.wg.Add(1) - go rl.cleanupLoop() - - return rl -} - -// getLimiter возвращает или создаёт лимитер для ключа -func (rl *RateLimiter) getLimiter(key string) *TokenBucket { - if val, ok := rl.limiters.Load(key); ok { - return val.(*TokenBucket) - } - - limiter := NewTokenBucket(float64(rl.config.RequestsPerSecond), rl.config.BurstSize) - actual, _ := rl.limiters.LoadOrStore(key, limiter) - return actual.(*TokenBucket) -} - -// Allow проверяет, разрешён ли запрос для ключа -func (rl *RateLimiter) Allow(key string) bool { - limiter := rl.getLimiter(key) - allowed := limiter.Allow() - - if allowed { - rl.allowed.Add(1) - } else { - rl.rejected.Add(1) - } - - return allowed -} - -// Middleware возвращает middleware для HTTP -func (rl *RateLimiter) Middleware(next http.HandlerFunc) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - // Используем IP + User-Agent как ключ - key := r.RemoteAddr - if forwarded := r.Header.Get("X-Forwarded-For"); forwarded != "" { - key = forwarded - } - - if !rl.Allow(key) { - w.Header().Set("X-RateLimit-Limit", fmt.Sprintf("%d", rl.config.RequestsPerSecond)) - w.Header().Set("X-RateLimit-Remaining", "0") - w.Header().Set("Retry-After", "1") - http.Error(w, "Rate limit exceeded. Please try again later.", http.StatusTooManyRequests) - return - } - - next(w, r) - } -} - -// cleanupLoop периодически очищает старые лимитеры -func (rl *RateLimiter) cleanupLoop() { - defer rl.wg.Done() - - ticker := time.NewTicker(rl.config.CleanupInterval) - defer ticker.Stop() - - for { - select { - case <-ticker.C: - rl.cleanup() - case <-rl.stopChan: - return - } - } -} - -// cleanup удаляет старые лимитеры -func (rl *RateLimiter) cleanup() { - count := 0 - rl.limiters.Range(func(key, value interface{}) bool { - count++ - if count > rl.config.MaxEntries { - rl.limiters.Delete(key) - } - return true - }) -} - -// GetStats возвращает статистику rate limiter'а -func (rl *RateLimiter) GetStats() map[string]interface{} { - count := 0 - rl.limiters.Range(func(key, value interface{}) bool { - count++ - return true - }) - - return map[string]interface{}{ - "active_limiters": count, - "allowed_requests": rl.allowed.Load(), - "rejected_requests": rl.rejected.Load(), - "requests_per_second": rl.config.RequestsPerSecond, - "burst_size": rl.config.BurstSize, - } -} - -// Stop останавливает rate limiter -func (rl *RateLimiter) Stop() { - close(rl.stopChan) - rl.wg.Wait() -}