-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgin_goll_test.go
119 lines (102 loc) · 3.35 KB
/
gin_goll_test.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
package limit
import (
"fmt"
"net/http"
"runtime"
"strconv"
"testing"
"time"
goll "github.com/fabiofenoglio/goll"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
)
func TestGinWithLoadLimiter(t *testing.T) {
r := gin.Default()
limiter, _ := goll.New(&goll.Config{
MaxLoad: 100,
WindowSize: 3 * time.Second,
})
// instantiate the limiter middleware using the limiter instance just created.
// we assign a default load of "1" per route
ginLimiter := NewLimiterMiddleware(Config{
// Limiter is the goll.LoadLimiter instance
Limiter: limiter,
// DefaultRouteLoad is the default load per each route
// when no route-specific configuration is available
DefaultRouteLoad: 1,
// TenantKeyFunc extracts the tenant key from the request context.
//
// For instance you can return the request origin IP
// if you want to limit the load on a per-IP basis,
// or you could return the username/id of an authenticated client.
//
// If you have a single tenant or want to limit globally
// you can return a fixed string or use the TenantKey parameter instead.
TenantKeyFunc: func(c *gin.Context) (string, error) {
// we will limit the load on a per-ip basis
return c.ClientIP(), nil
},
// AbortHandler decides how we respond when a request
// exceeds the load limit
AbortHandler: func(c *gin.Context, result goll.SubmitResult) {
if result.RetryInAvailable {
c.Header("X-Retry-In", fmt.Sprintf("%v", result.RetryIn.Milliseconds()))
}
c.AbortWithStatus(429)
},
// ErrorHandler is optional
ErrorHandler: func(c *gin.Context, err error) {
c.AbortWithStatusJSON(500, err.Error())
},
})
// plugin the load limiter middleware for all routes like this:
// r.Use(ginLimiter.Default())
// and/or on single routes, with route-specific configuration, like this:
// r.GET("/", ginLimiter.WithLoad(10), routeHandler)
r.GET("/", ginLimiter.WithLoad(10), func(c *gin.Context) {})
go func() {
err := r.Run(":9000")
if err != nil {
t.Error("error running the test http server", err.Error())
}
}()
runtime.Gosched()
// let's run a series of requests and check that
// the limiter breaks and restores in the expected way.
for i := 0; i < 15; i++ {
resp, err := http.DefaultClient.Get("http://127.0.0.1:9000")
if err != nil {
t.Error("unexpected error in http request", err.Error())
return
}
switch {
case i < 10:
if resp.StatusCode != 200 {
t.Errorf("unexpected status code %v", resp.StatusCode)
}
case i == 10:
if resp.StatusCode != 429 {
t.Errorf("expected 429, got %v", resp.StatusCode)
} else {
// if we call from another ip we are allowed
// because we are using the IP as tenant key
req2, _ := http.NewRequest("GET", "http://127.0.0.1:9000", nil)
req2.RemoteAddr = "127.0.0.15"
req2.Header.Add("X-Forwarded-For", req2.RemoteAddr)
resp2, _ := http.DefaultClient.Do(req2)
assert.Equal(t, 200, resp2.StatusCode)
// check the retry-in header
retryHeader := resp.Header["X-Retry-In"]
parsed, _ := strconv.Atoi(retryHeader[0])
assert.Greater(t, parsed, 0)
// wait the required amount of time
t.Logf("got retry-in header \"%s\" asking for %v ms", retryHeader[0], parsed)
time.Sleep(time.Duration(parsed) * time.Millisecond)
}
case i > 10:
if resp.StatusCode == 429 {
t.Error("unexpected 429 after waiting")
}
}
}
}