-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathlimiter.go
126 lines (110 loc) · 2.65 KB
/
limiter.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
package fiber_limiter
import (
"github.com/gofiber/fiber"
"github.com/kiyonlin/rate"
"math"
"strconv"
"sync"
)
var (
limiters = make(map[string]*rate.Limiter)
mu sync.Mutex
)
// Config ...
type Config struct {
// Filter defines a function to skip middleware.
// Optional. Default: nil
Filter func(*fiber.Ctx) bool
// Limit defines the maximum frequency of requests.
// Limit is represented as integer of requests per second.
// Default: 10
Limit int
// Burst is maximum burst size
// Default: 10
Burst int
// Message
// default: "Too many requests, please try again later."
Message string
// StatusCode
// Default: 429 Too Many Requests
StatusCode int
// Key allows to use a custom handler to create custom keys
// Default: func(c *fiber.Ctx) string {
// return c.IP()
// }
Key func(*fiber.Ctx) string
// Handler is called when a request hits the limit
// Default: func(c *fiber.Ctx) {
// c.Status(cfg.StatusCode).Format(cfg.Message)
// }
Handler func(*fiber.Ctx)
}
// New ...
func New(config ...Config) func(*fiber.Ctx) {
// Init config
var cfg Config
if len(config) > 0 {
cfg = config[0]
}
if cfg.Limit == 0 {
cfg.Limit = 10
}
if cfg.Burst == 0 {
cfg.Burst = 10
}
if cfg.Message == "" {
cfg.Message = "Too many requests, please try again later."
}
if cfg.StatusCode == 0 {
cfg.StatusCode = 429
}
if cfg.Key == nil {
cfg.Key = func(c *fiber.Ctx) string {
return c.IP()
}
}
if cfg.Handler == nil {
cfg.Handler = func(c *fiber.Ctx) {
c.Status(cfg.StatusCode).Format(cfg.Message)
}
}
return func(c *fiber.Ctx) {
// Filter request to skip middleware
if cfg.Filter != nil && cfg.Filter(c) {
c.Next()
return
}
key := cfg.Key(c)
mu.Lock()
lim, ok := limiters[key]
if !ok {
// Get a default limiter
lim = rate.NewLimiter(rate.Limit(cfg.Limit), cfg.Burst)
limiters[key] = lim
}
mu.Unlock()
// Try to request
r := lim.Reserve()
// Check reservation's delay
if d := r.Delay(); d > 0 {
cfg.Handler(c)
// Return response with Retry-After header
// https://tools.ietf.org/html/rfc7231#section-7.1.3
// Set second value(at least one) to Retry-After header
c.Set(fiber.HeaderRetryAfter, strconv.FormatInt(int64(math.Ceil(d.Seconds())), 10))
return
}
// We can continue, update RateLimit headers
c.Set("X-RateLimit-Limit", strconv.Itoa(lim.Burst()))
c.Set("X-RateLimit-Remaining", strconv.Itoa(r.RemainedTokens()))
c.Set("X-RateLimit-Reset", strconv.FormatInt(int64(math.Ceil(r.Reset().Seconds())), 10))
// Bye!
c.Next()
}
}
// Set sets custom limiter for a specific key
func Set(key string, lim *rate.Limiter) {
mu.Lock()
limiters[key] = lim
mu.Unlock()
}