Files
llama-link/internal/api/middleware/ratelimit.go
T
darroyo 4c9ed3c24b Initial commit: LlamaLink Go rewrite
Complete rewrite from Python/FastAPI to Go/Gin:
- Go backend: auth (API keys + bcrypt), llama.cpp subprocess manager,
  hot-swap multi-model, rate limiting, quota system, webhooks
- Vue 3 SPA admin panel (src/) with Tailwind CSS
- Deployment: Docker multi-stage, docker-compose, nginx, systemd
- GORM/SQLite models: ApiKey, Model, UsageLog, Quota, Webhook
- REST API: /api/v1/admin/* (keys, models, chat, usage, health)
- Embedded frontend via go:embed (build output at web/dist/)

Removed legacy Python artifacts (app/, tests/, pyproject.toml, etc.)
2026-07-30 10:58:55 -04:00

86 lines
1.5 KiB
Go

package middleware
import (
"net/http"
"sync"
"time"
"github.com/gin-gonic/gin"
"golang.org/x/time/rate"
)
type RateLimiter struct {
visitors map[string]*visitor
mu sync.RWMutex
rate rate.Limit
burst int
}
type visitor struct {
limiter *rate.Limiter
lastSeen time.Time
}
func NewRateLimiter(requestsPerMinute int) *RateLimiter {
rl := &RateLimiter{
visitors: make(map[string]*visitor),
rate: rate.Limit(float64(requestsPerMinute) / 60.0),
burst: requestsPerMinute / 10,
}
if requestsPerMinute < 10 {
rl.burst = 1
}
// Cleanup old visitors
go rl.cleanup()
return rl
}
func (rl *RateLimiter) cleanup() {
ticker := time.NewTicker(5 * time.Minute)
for range ticker.C {
rl.mu.Lock()
for ip, v := range rl.visitors {
if time.Since(v.lastSeen) > 10*time.Minute {
delete(rl.visitors, ip)
}
}
rl.mu.Unlock()
}
}
func (rl *RateLimiter) getVisitor(ip string) *rate.Limiter {
rl.mu.Lock()
defer rl.mu.Unlock()
v, exists := rl.visitors[ip]
if !exists {
v = &visitor{
limiter: rate.NewLimiter(rl.rate, rl.burst),
lastSeen: time.Now(),
}
rl.visitors[ip] = v
}
v.lastSeen = time.Now()
return v.limiter
}
func RateLimitMiddleware(rl *RateLimiter) gin.HandlerFunc {
return func(c *gin.Context) {
ip := c.ClientIP()
if !rl.getVisitor(ip).Allow() {
c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{
"error": gin.H{
"code": "rate_limit_exceeded",
"message": "Too many requests",
"retry_after_seconds": 60,
},
})
return
}
c.Next()
}
}