Skip to content

Commit 8d34f11

Browse files
committed
Added last seen stats for visitor
1 parent 02efca7 commit 8d34f11

File tree

2 files changed

+91
-41
lines changed

2 files changed

+91
-41
lines changed

middleware/rate_limiter.go

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"golang.org/x/time/rate"
66
"net/http"
77
"sync"
8+
"time"
89
)
910

1011
// RateLimiterStore is the interface to be implemented by custom stores.
@@ -73,27 +74,34 @@ func RateLimiterWithConfig(config RateLimiterConfig) echo.MiddlewareFunc {
7374
}
7475

7576
// RateLimiterMemoryStore is the built-in store implementation for RateLimiter
76-
type RateLimiterMemoryStore struct {
77-
visitors map[string]*rate.Limiter
78-
mutex sync.Mutex
79-
rate rate.Limit
80-
burst int
81-
}
77+
type (
78+
RateLimiterMemoryStore struct {
79+
visitors map[string]visitor
80+
mutex sync.Mutex
81+
rate rate.Limit
82+
burst int
83+
}
84+
visitor struct {
85+
*rate.Limiter
86+
lastSeen time.Time
87+
}
88+
)
8289

8390
// Allow implements RateLimiterStore.Allow
8491
func (store *RateLimiterMemoryStore) Allow(identifier string) bool {
8592
store.mutex.Lock()
8693

8794
if store.visitors == nil {
88-
store.visitors = make(map[string]*rate.Limiter)
95+
store.visitors = make(map[string]visitor)
8996
}
9097

9198
limiter, exists := store.visitors[identifier]
9299
if !exists {
93-
limiter = rate.NewLimiter(store.rate, store.burst)
100+
limiter.Limiter = rate.NewLimiter(store.rate, store.burst)
101+
limiter.lastSeen = time.Now()
94102
store.visitors[identifier] = limiter
95103
}
96-
104+
limiter.lastSeen = time.Now()
97105
store.mutex.Unlock()
98106
return limiter.Allow()
99107
}

middleware/rate_limiter_test.go

Lines changed: 74 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -119,46 +119,88 @@ func TestRateLimiter(t *testing.T) {
119119
}
120120

121121
func TestRateLimiterWithConfig(t *testing.T) {
122-
var inMemoryStore = new(RateLimiterMemoryStore)
123-
inMemoryStore.rate = 1
124-
inMemoryStore.burst = 3
125-
126-
e := echo.New()
122+
{
123+
var inMemoryStore = new(RateLimiterMemoryStore)
124+
inMemoryStore.rate = 1
125+
inMemoryStore.burst = 3
127126

128-
handler := func(c echo.Context) error {
129-
return c.String(http.StatusOK, "test")
127+
e := echo.New()
128+
129+
handler := func(c echo.Context) error {
130+
return c.String(http.StatusOK, "test")
131+
}
132+
133+
testCases := []struct {
134+
id string
135+
code int
136+
}{
137+
{"127.0.0.1", 200},
138+
{"127.0.0.1", 200},
139+
{"127.0.0.1", 200},
140+
{"127.0.0.1", 429},
141+
{"127.0.0.1", 429},
142+
{"127.0.0.1", 429},
143+
{"127.0.0.1", 429},
144+
}
145+
146+
for _, tc := range testCases {
147+
req := httptest.NewRequest(http.MethodGet, "/", nil)
148+
req.Header.Add(echo.HeaderXRealIP, tc.id)
149+
150+
rec := httptest.NewRecorder()
151+
152+
c := e.NewContext(req, rec)
153+
mw := RateLimiterWithConfig(RateLimiterConfig{
154+
SourceFunc: func(c echo.Context) string {
155+
return c.RealIP()
156+
},
157+
Store: inMemoryStore,
158+
})
159+
160+
_ = mw(handler)(c)
161+
162+
assert.Equal(t, tc.code, rec.Code)
163+
}
130164
}
165+
{
166+
var inMemoryStore = new(RateLimiterMemoryStore)
167+
inMemoryStore.rate = 1
168+
inMemoryStore.burst = 3
131169

132-
testCases := []struct {
133-
id string
134-
code int
135-
}{
136-
{"127.0.0.1", 200},
137-
{"127.0.0.1", 200},
138-
{"127.0.0.1", 200},
139-
{"127.0.0.1", 429},
140-
{"127.0.0.1", 429},
141-
{"127.0.0.1", 429},
142-
{"127.0.0.1", 429},
143-
}
170+
e := echo.New()
144171

145-
for _, tc := range testCases {
146-
req := httptest.NewRequest(http.MethodGet, "/", nil)
147-
req.Header.Add(echo.HeaderXRealIP, tc.id)
172+
handler := func(c echo.Context) error {
173+
return c.String(http.StatusOK, "test")
174+
}
148175

149-
rec := httptest.NewRecorder()
176+
testCases := []struct {
177+
id string
178+
code int
179+
}{
180+
{"127.0.0.1", 200},
181+
{"127.0.0.1", 200},
182+
{"127.0.0.1", 200},
183+
{"127.0.0.1", 429},
184+
{"127.0.0.1", 429},
185+
{"127.0.0.1", 429},
186+
{"127.0.0.1", 429},
187+
}
150188

151-
c := e.NewContext(req, rec)
152-
mw := RateLimiterWithConfig(RateLimiterConfig{
153-
SourceFunc: func(c echo.Context) string {
154-
return c.RealIP()
155-
},
156-
Store: inMemoryStore,
157-
})
189+
for _, tc := range testCases {
190+
req := httptest.NewRequest(http.MethodGet, "/", nil)
191+
req.Header.Add(echo.HeaderXRealIP, tc.id)
158192

159-
_ = mw(handler)(c)
193+
rec := httptest.NewRecorder()
160194

161-
assert.Equal(t, tc.code, rec.Code)
195+
c := e.NewContext(req, rec)
196+
mw := RateLimiterWithConfig(RateLimiterConfig{
197+
Store: inMemoryStore,
198+
})
199+
200+
_ = mw(handler)(c)
201+
202+
assert.Equal(t, tc.code, rec.Code)
203+
}
162204
}
163205
}
164206

0 commit comments

Comments
 (0)