github.com/angenalZZZ/gofunc@v0.0.0-20210507121333-48ff1be3917b/http/fast/middleware/limiter/ratelimiter.go (about) 1 package limiter 2 3 import ( 4 "errors" 5 "github.com/angenalZZZ/gofunc/f" 6 "github.com/angenalZZZ/gofunc/http/fast" 7 "github.com/valyala/fasthttp" 8 "strconv" 9 "time" 10 ) 11 12 // ErrTooManyRequests is returned when too many requests. 13 var ErrTooManyRequests = errors.New("too many requests") 14 15 // ErrRateLimitHeader response when any requests. 16 type ErrRateLimitHeader struct { 17 Allowed bool 18 Header RateLimitHeader 19 } 20 type RateLimitHeader struct { 21 Limit, Remaining, Reset, RetryAfter int64 22 } 23 24 // ResponseRateLimitHeader response when any requests. 25 func ResponseRateLimitHeader(c *fast.Ctx, opt *ErrRateLimitHeader) { 26 //c.SetHeader("X-Rate-Limit-Duration", "1") 27 if opt.Allowed { 28 c.SetHeader("X-RateLimit-Limit", strconv.FormatInt(opt.Header.Limit, 10)) 29 c.SetHeader("X-RateLimit-Remaining", strconv.FormatInt(opt.Header.Remaining, 10)) 30 c.SetHeader("X-RateLimit-Reset", strconv.FormatInt(opt.Header.Reset, 10)) 31 } else { 32 // Return response with Retry-After header 33 // https://tools.ietf.org/html/rfc6584 34 c.SetHeader("Retry-After", strconv.FormatInt(opt.Header.RetryAfter, 10)) 35 } 36 } 37 38 // Config defines the config for limiter middleware 39 type Config struct { 40 // Filter defines a function to skip middleware. 41 // Optional. Default: nil 42 Filter func(*fast.Ctx) bool 43 // Timeout in seconds on how long to keep records of requests in memory 44 // Default: 1 45 Timeout int64 46 // Max number of recent connections during `Timeout` seconds before sending a 429 response 47 // Default: 100 48 Max int64 49 // Message 50 // default: "Too many requests, please try again later." 51 Message string 52 // StatusCode 53 // Default: 429 Too Many Requests 54 StatusCode int 55 // Key allows to use a custom handler to create custom keys 56 // Default: func(c *fast.Ctx) string { 57 // return c.IP() 58 // } 59 Key func(*fast.Ctx) string 60 // Handler is called when a request hits the limit 61 // Default: func(c *fast.Ctx) { 62 // c.Status(cfg.StatusCode).SendString(cfg.Message) 63 // } 64 Handler func(*fast.Ctx) 65 } 66 67 // New middleware. 68 // cfg := limiter.Config{ 69 // Max: 100, 70 // } 71 // app.Use(limiter.New(cfg)) 72 func New(config ...Config) func(*fast.Ctx) { 73 // Init config 74 var cfg Config 75 if len(config) > 0 { 76 cfg = config[0] 77 } 78 if cfg.Timeout == 0 { 79 cfg.Timeout = 1 80 } 81 if cfg.Max == 0 { 82 cfg.Max = 100 83 } 84 if cfg.Message == "" { 85 cfg.Message = ErrTooManyRequests.Error() 86 } 87 if cfg.StatusCode == 0 { 88 cfg.StatusCode = fasthttp.StatusTooManyRequests 89 } 90 if cfg.Key == nil { 91 cfg.Key = func(c *fast.Ctx) string { 92 return c.IP() 93 } 94 } 95 if cfg.Handler == nil { 96 cfg.Handler = func(c *fast.Ctx) { 97 c.Status(cfg.StatusCode).SendString(cfg.Message) 98 } 99 } 100 // Limiter settings 101 var hits = f.NewConcurrentMap() 102 var reset = f.NewConcurrentMap() 103 var timestamp = time.Now().UnixNano() 104 // Update timestamp every second 105 go func() { 106 for { 107 timestamp = time.Now().UnixNano() 108 time.Sleep(time.Microsecond) 109 } 110 }() 111 // Reset hits every cfg.Timeout 112 go func() { 113 var zero int64 114 sleep := time.Duration(cfg.Timeout) * time.Second 115 for { 116 // For every key in reset 117 for item := range reset.IterBuffered() { 118 // If resetTime exist and current time is equal or bigger 119 i := item.Val.(int64) 120 if i != zero && timestamp >= i { 121 // Reset hits and resetTime 122 hits.Set(item.Key, zero) 123 reset.Set(item.Key, zero) 124 } 125 } 126 // Wait cfg.Timeout 127 time.Sleep(sleep) 128 } 129 }() 130 return func(c *fast.Ctx) { 131 // Filter request to skip middleware 132 if cfg.Filter != nil && cfg.Filter(c) { 133 c.Next() 134 return 135 } 136 // GetHeader key (default is the remote IP) 137 key := cfg.Key(c) 138 // SetHeader unix timestamp if not exist 139 var hitReset int64 140 if hit, ok := reset.Get(key); ok { 141 hitReset = hit.(int64) 142 } else { 143 hitReset = timestamp + cfg.Timeout 144 reset.Set(key, hitReset) 145 } 146 // Increment key hits 147 var hitCount int64 148 if hit, ok := hits.Get(key); ok && hitReset != 0 { 149 hitCount = hit.(int64) + 1 150 } else { 151 hitCount = 1 152 } 153 hits.Set(key, hitCount) 154 // SetHeader how many hits we have left 155 remaining := cfg.Max - hitCount 156 // Calculate when it resets in seconds 157 resetTime := hitReset - timestamp 158 // Check if hits exceed the cfg.Max 159 if remaining < 0 { 160 // Call Handler func 161 cfg.Handler(c) 162 // Return response with Retry-After header 163 ResponseRateLimitHeader(c, &ErrRateLimitHeader{Allowed: false, Header: RateLimitHeader{RetryAfter: resetTime}}) 164 return 165 } 166 // We can continue, update RateLimit headers 167 ResponseRateLimitHeader(c, &ErrRateLimitHeader{ 168 Allowed: true, 169 Header: RateLimitHeader{ 170 Limit: cfg.Max, 171 Remaining: remaining, 172 Reset: resetTime, 173 }, 174 }) 175 c.Next() 176 } 177 }