1
0
mirror of https://github.com/gofiber/fiber.git synced 2025-02-23 14:24:11 +00:00
fiber/middleware/limiter/limiter.go

116 lines
2.5 KiB
Go
Raw Normal View History

2020-09-13 11:20:11 +02:00
package limiter
import (
"strconv"
2020-11-23 07:38:42 +01:00
"sync"
2020-09-15 20:39:39 +02:00
"sync/atomic"
2020-09-13 11:20:11 +02:00
"time"
"github.com/gofiber/fiber/v2"
)
const (
2020-11-11 13:54:27 +01:00
// Storage ErrNotExist
errNotExist = "key does not exist"
// X-RateLimit-* headers
2020-09-13 11:20:11 +02:00
xRateLimitLimit = "X-RateLimit-Limit"
xRateLimitRemaining = "X-RateLimit-Remaining"
xRateLimitReset = "X-RateLimit-Reset"
)
// New creates a new middleware handler
func New(config ...Config) fiber.Handler {
// Set default config
2020-11-11 13:54:27 +01:00
cfg := configDefault(config...)
2020-09-13 11:20:11 +02:00
2020-10-27 08:12:53 +01:00
var (
2020-11-23 07:38:42 +01:00
// Limiter variables
mux = &sync.RWMutex{}
2020-10-27 08:12:53 +01:00
max = strconv.Itoa(cfg.Max)
timestamp = uint64(time.Now().Unix())
expiration = uint64(cfg.Expiration.Seconds())
)
2020-09-13 11:20:11 +02:00
2020-11-23 07:38:42 +01:00
// Create manager to simplify storage operations ( see manager.go )
manager := newManager(cfg.Storage)
2020-11-20 11:43:07 +01:00
2020-09-15 20:28:15 +02:00
// Update timestamp every second
go func() {
for {
2020-09-15 20:53:29 +02:00
atomic.StoreUint64(&timestamp, uint64(time.Now().Unix()))
2020-09-16 22:16:41 +02:00
time.Sleep(1 * time.Second)
2020-09-15 20:28:15 +02:00
}
}()
2020-09-13 11:20:11 +02:00
// Return new handler
return func(c *fiber.Ctx) error {
// Don't execute middleware if Next returns true
if cfg.Next != nil && cfg.Next(c) {
return c.Next()
}
// Continue stack for reaching c.Response().StatusCode()
// Store err for returning
err := c.Next()
2020-10-28 02:29:47 +01:00
// Get key from request
2020-11-11 18:28:27 +01:00
key := cfg.KeyGenerator(c)
2020-09-13 11:20:11 +02:00
2020-11-23 07:44:06 +01:00
// Lock entry
mux.Lock()
2020-11-23 07:38:42 +01:00
// Get entry from pool and release when finished
e := manager.get(key)
2020-10-28 02:29:47 +01:00
// Get timestamp
2020-09-15 20:53:29 +02:00
ts := atomic.LoadUint64(&timestamp)
2020-10-28 02:29:47 +01:00
// Set expiration if entry does not exist
2020-11-20 11:43:07 +01:00
if e.exp == 0 {
e.exp = ts + expiration
2020-10-28 02:29:47 +01:00
2020-11-20 11:43:07 +01:00
} else if ts >= e.exp {
2020-10-28 02:29:47 +01:00
// Check if entry is expired
2020-11-20 11:43:07 +01:00
e.hits = 0
e.exp = ts + expiration
2020-09-13 11:20:11 +02:00
}
// Check for SkipFailedRequests and SkipSuccessfulRequests
if (!cfg.SkipSuccessfulRequests || c.Response().StatusCode() >= 400) &&
(!cfg.SkipFailedRequests || c.Response().StatusCode() < 400) {
// Increment hits
e.hits++
}
2020-11-20 11:43:07 +01:00
2020-09-13 11:20:11 +02:00
// Calculate when it resets in seconds
2020-11-20 11:43:07 +01:00
expire := e.exp - ts
2020-09-13 11:20:11 +02:00
// Set how many hits we have left
2020-11-20 11:43:07 +01:00
remaining := cfg.Max - e.hits
2020-11-23 07:38:42 +01:00
// Update storage
manager.set(key, e, cfg.Expiration)
2020-11-23 07:44:06 +01:00
// Unlock entry
mux.Unlock()
2020-09-13 11:20:11 +02:00
// Check if hits exceed the cfg.Max
if remaining < 0 {
// Return response with Retry-After header
// https://tools.ietf.org/html/rfc6584
2020-10-27 08:12:53 +01:00
c.Set(fiber.HeaderRetryAfter, strconv.FormatUint(expire, 10))
2020-09-13 11:20:11 +02:00
// Call LimitReached handler
return cfg.LimitReached(c)
}
// We can continue, update RateLimit headers
c.Set(xRateLimitLimit, max)
c.Set(xRateLimitRemaining, strconv.Itoa(remaining))
2020-10-27 08:12:53 +01:00
c.Set(xRateLimitReset, strconv.FormatUint(expire, 10))
2020-09-13 11:20:11 +02:00
return err
2020-09-13 11:20:11 +02:00
}
}