github.com/goravel/framework@v1.13.9/http/middleware/throttle.go (about) 1 package middleware 2 3 import ( 4 "crypto/md5" 5 "encoding/hex" 6 "fmt" 7 "strconv" 8 "time" 9 10 httpcontract "github.com/goravel/framework/contracts/http" 11 "github.com/goravel/framework/http" 12 httplimit "github.com/goravel/framework/http/limit" 13 "github.com/goravel/framework/support/carbon" 14 ) 15 16 func Throttle(name string) httpcontract.Middleware { 17 return func(ctx httpcontract.Context) { 18 if limiter := http.RateLimiterFacade.Limiter(name); limiter != nil { 19 if limits := limiter(ctx); len(limits) > 0 { 20 for _, limit := range limits { 21 if instance, ok := limit.(*httplimit.Limit); ok { 22 key, timer := key(ctx, name, instance) 23 currentTimes := 1 24 25 if http.CacheFacade.Has(timer) { 26 value := http.CacheFacade.GetInt(key, 0) 27 if value >= instance.MaxAttempts { 28 expireSecond := http.CacheFacade.GetInt(timer, 0) + instance.DecayMinutes*60 29 ctx.Response().Header("X-RateLimit-Reset", strconv.Itoa(expireSecond)) 30 ctx.Response().Header("Retry-After", strconv.Itoa(expireSecond-int(carbon.Now().Timestamp()))) 31 if instance.ResponseCallback != nil { 32 instance.ResponseCallback(ctx) 33 return 34 } else { 35 ctx.Request().AbortWithStatus(httpcontract.StatusTooManyRequests) 36 return 37 } 38 } else { 39 var err error 40 if currentTimes, err = http.CacheFacade.Increment(key); err != nil { 41 panic(err) 42 } 43 } 44 } else { 45 expireMinute := time.Duration(instance.DecayMinutes) * time.Minute 46 47 err := http.CacheFacade.Put(timer, carbon.Now().Timestamp(), expireMinute) 48 if err != nil { 49 panic(err) 50 } 51 52 err = http.CacheFacade.Put(key, currentTimes, expireMinute) 53 if err != nil { 54 panic(err) 55 } 56 } 57 58 // add the headers for the passed request 59 ctx.Response().Header("X-RateLimit-Limit", strconv.Itoa(instance.MaxAttempts)) 60 ctx.Response().Header("X-RateLimit-Remaining", strconv.Itoa(instance.MaxAttempts-currentTimes)) 61 } 62 } 63 } 64 } 65 66 ctx.Request().Next() 67 } 68 } 69 70 func key(ctx httpcontract.Context, limiter string, limit *httplimit.Limit) (string, string) { 71 // if no key is set, use the path and ip address as the default key 72 var key, timer string 73 prefix := http.ConfigFacade.GetString("cache.prefix") 74 if len(limit.Key) == 0 { 75 hash := md5.Sum([]byte(ctx.Request().Path())) 76 key = fmt.Sprintf("%s:throttle:%s:%s:%s", prefix, limiter, hex.EncodeToString(hash[:]), ctx.Request().Ip()) 77 } else { 78 hash := md5.Sum([]byte(limit.Key)) 79 key = fmt.Sprintf("%s:throttle:%s:%s", prefix, limiter, hex.EncodeToString(hash[:])) 80 } 81 timer = key + ":timer" 82 83 return key, timer 84 }