The codebase that powers boop.cat boop.cat
at main 80 lines 1.4 kB view raw
1// Copyright 2025 boop.cat 2// Licensed under the Apache License, Version 2.0 3// See LICENSE file for details. 4 5package middleware 6 7import ( 8 "net/http" 9 "sync" 10 "time" 11) 12 13type visitor struct { 14 limiter *rateLimiter 15 lastSeen time.Time 16} 17 18type rateLimiter struct { 19 rate int 20 per time.Duration 21 tokens int 22 last time.Time 23 mu sync.Mutex 24} 25 26func newRateLimiter(r int, d time.Duration) *rateLimiter { 27 return &rateLimiter{ 28 rate: r, 29 per: d, 30 tokens: r, 31 last: time.Now(), 32 } 33} 34 35func (l *rateLimiter) Allow() bool { 36 l.mu.Lock() 37 defer l.mu.Unlock() 38 39 now := time.Now() 40 41 elapsed := now.Sub(l.last) 42 if elapsed > l.per { 43 l.tokens = l.rate 44 l.last = now 45 } 46 47 if l.tokens > 0 { 48 l.tokens-- 49 return true 50 } 51 return false 52} 53 54var ( 55 visitors = make(map[string]*rateLimiter) 56 mu sync.Mutex 57) 58 59func RateLimit(requests int, window time.Duration) func(http.Handler) http.Handler { 60 return func(next http.Handler) http.Handler { 61 62 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 63 ip := r.RemoteAddr 64 65 mu.Lock() 66 limiter, exists := visitors[ip] 67 if !exists { 68 limiter = newRateLimiter(requests, window) 69 visitors[ip] = limiter 70 } 71 mu.Unlock() 72 73 if !limiter.Allow() { 74 http.Error(w, "Rate limit exceeded", http.StatusTooManyRequests) 75 return 76 } 77 next.ServeHTTP(w, r) 78 }) 79 } 80}