This repository has been archived by the owner on Sep 17, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 5
/
main.go
128 lines (122 loc) · 3.07 KB
/
main.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
127
128
// 🚀 Fiber is an Express inspired web framework written in Go with 💖
// 📌 API Documentation: https://fiber.wiki
// 📝 Github Repository: https://github.com/gofiber/fiber
package limiter
import (
"strconv"
"sync"
"time"
"github.com/gofiber/fiber"
)
// Config ...
type Config struct {
// Filter defines a function to skip middleware.
// Optional. Default: nil
Filter func(*fiber.Ctx) bool
// Timeout in seconds on how long to keep records of requests in memory
// Default: 60
Timeout int
// Max number of recent connections during `Timeout` seconds before sending a 429 response
// Default: 10
Max 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).SendString(cfg.Message)
// }
Handler func(*fiber.Ctx)
}
// New ...
func New(config ...Config) func(*fiber.Ctx) {
// mutex for parallel read and write access
mux := &sync.Mutex{}
// Init config
var cfg Config
if len(config) > 0 {
cfg = config[0]
}
if cfg.Timeout == 0 {
cfg.Timeout = 60
}
if cfg.Max == 0 {
cfg.Max = 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).SendString(cfg.Message)
}
}
// Limiter settings
var hits = make(map[string]int)
var reset = make(map[string]int)
var timestamp = int(time.Now().Unix())
// Update timestamp every second
go func() {
for {
timestamp = int(time.Now().Unix())
time.Sleep(1 * time.Second)
}
}()
return func(c *fiber.Ctx) {
// Filter request to skip middleware
if cfg.Filter != nil && cfg.Filter(c) {
c.Next()
return
}
// Get key (default is the remote IP)
key := cfg.Key(c)
mux.Lock()
// Set unix timestamp if not exist
if reset[key] == 0 {
reset[key] = timestamp + cfg.Timeout
} else if timestamp >= reset[key] {
hits[key] = 0
reset[key] = timestamp + cfg.Timeout
}
// Increment key hits
hits[key]++
// Get current hits
hitCount := hits[key]
// Calculate when it resets in seconds
resetTime := reset[key] - timestamp
mux.Unlock()
// Set how many hits we have left
remaining := cfg.Max - hitCount
// Check if hits exceed the cfg.Max
if remaining < 0 {
// Call Handler func
cfg.Handler(c)
// Return response with Retry-After header
// https://tools.ietf.org/html/rfc6584
c.Set("Retry-After", strconv.Itoa(resetTime))
return
}
// We can continue, update RateLimit headers
c.Set("X-RateLimit-Limit", strconv.Itoa(cfg.Max))
c.Set("X-RateLimit-Remaining", strconv.Itoa(remaining))
c.Set("X-RateLimit-Reset", strconv.Itoa(resetTime))
// Bye!
c.Next()
}
}