Skip to content

Commit a3e8fe0

Browse files
committed
✨ feat: added config Rate Limiting and Throttling #15
1 parent 90a6692 commit a3e8fe0

File tree

6 files changed

+115
-1
lines changed

6 files changed

+115
-1
lines changed

config/conf-params.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,3 +101,16 @@ curl:
101101
chat_id:
102102
- 123456789
103103
token: <token_here>
104+
# ################################
105+
# Rate Limit Seekers Config
106+
# 2023-11-25 12:02:54
107+
# ################################
108+
rate-limit-seekers:
109+
- key: "psql_rate"
110+
usable_default: false
111+
config:
112+
enabled: false
113+
rate: 2
114+
max_burst: 1
115+
option:
116+
max_retries: 2

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ require (
1515
github.com/sivaosorg/redisconn v1.0.2
1616
github.com/sivaosorg/wsconn v1.0.3-beta.1
1717
github.com/swaggo/swag v1.16.2
18+
golang.org/x/time v0.4.0
1819
)
1920

2021
require (

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
208208
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
209209
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
210210
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
211+
golang.org/x/time v0.4.0 h1:Z81tqI5ddIoXDPvVQ7/7CC9TnLM7ubaFG2qXYd5BbYY=
212+
golang.org/x/time v0.4.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
211213
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
212214
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
213215
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=

internal/core/routes.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ func (c *CoreCommand) routes(core *gin.Engine) {
1616
v1.GET("/common/psql-status",
1717
c.handlers.middlewares.RequestMiddleWare(),
1818
c.handlers.middlewares.NoopMiddleWare(),
19+
c.handlers.middlewares.RateLimitMiddleWare("psql_rate"),
20+
c.handlers.middlewares.NetMiddleware(),
1921
c.handlers.commonHandler.OnPsqlStatus)
2022
v1.GET("/common/consumer", // endpoint websocket: ws://127.0.0.1:8081/api/v1/common/consumer
2123
c.handlers.middlewares.RequestMiddleWare(),
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package middlewares
2+
3+
import (
4+
"fmt"
5+
"net"
6+
"net/http"
7+
"sync"
8+
"time"
9+
10+
"github.com/gin-gonic/gin"
11+
syncconf "github.com/sivaosorg/gocell/internal/syncConf"
12+
"github.com/sivaosorg/govm/entity"
13+
"github.com/sivaosorg/govm/logger"
14+
"github.com/sivaosorg/govm/ratelimitx"
15+
"golang.org/x/time/rate"
16+
)
17+
18+
// https://blog.logrocket.com/rate-limiting-go-application/
19+
type client struct {
20+
limiter *rate.Limiter
21+
last time.Time
22+
}
23+
24+
var (
25+
mu sync.Mutex
26+
clients = make(map[string]*client)
27+
cleanupTypeWhitelist = time.Minute
28+
cleanupWhitelist = 2 * cleanupTypeWhitelist
29+
)
30+
31+
func (m *MiddlewareManager) RateLimitMiddleWare(key string) gin.HandlerFunc {
32+
rates := syncconf.Params.RateLimits
33+
clusters := ratelimitx.NewClusterMultiTenantRateLimitConfig().SetClusters(rates)
34+
go m.cleanupWhitelist()
35+
return func(c *gin.Context) {
36+
conf, err := clusters.FindClusterBy(key)
37+
if err != nil || !conf.Config.IsEnabled {
38+
return
39+
}
40+
ip, err := m.decodeNetwork(c)
41+
if err != nil {
42+
response := entity.NewResponseEntity().BadRequest(http.StatusText(http.StatusBadRequest), nil)
43+
response.SetError(err)
44+
c.JSON(response.StatusCode, response)
45+
c.Abort()
46+
return
47+
}
48+
mu.Lock()
49+
if _, found := clients[ip]; !found {
50+
clients[ip] = &client{limiter: rate.NewLimiter(rate.Limit(conf.Config.Rate), conf.Config.MaxBurst)}
51+
}
52+
clients[ip].last = time.Now()
53+
if !clients[ip].limiter.Allow() {
54+
mu.Unlock()
55+
response := entity.NewResponseEntity().TooManyRequest(http.StatusText(http.StatusTooManyRequests), nil)
56+
c.JSON(response.StatusCode, response)
57+
c.Abort()
58+
return
59+
}
60+
mu.Unlock()
61+
c.Next()
62+
}
63+
}
64+
65+
func (m *MiddlewareManager) NetMiddleware() gin.HandlerFunc {
66+
return func(c *gin.Context) {
67+
ip, err := m.decodeNetwork(c)
68+
if err != nil {
69+
m.notification(c, err, http.StatusBadRequest)
70+
}
71+
logger.Debugf(fmt.Sprintf("_endpoint: %v, incoming on IP: %v", c.Request.RequestURI, ip))
72+
c.Next()
73+
}
74+
}
75+
76+
func (m *MiddlewareManager) decodeNetwork(c *gin.Context) (ip string, err error) {
77+
ip, _, err = net.SplitHostPort(c.Request.RemoteAddr)
78+
return ip, err
79+
}
80+
81+
func (m *MiddlewareManager) cleanupWhitelist() {
82+
for {
83+
time.Sleep(cleanupTypeWhitelist)
84+
mu.Lock()
85+
for ip, client := range clients {
86+
remain := time.Since(client.last)
87+
if remain > cleanupWhitelist {
88+
logger.Debugf(fmt.Sprintf("Cleanup whitelist too many requests for IP: %v and remain duration: %v", ip, remain))
89+
delete(clients, ip)
90+
}
91+
}
92+
mu.Unlock()
93+
}
94+
}

internal/syncConf/syncConf.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"github.com/sivaosorg/govm/apix"
77
"github.com/sivaosorg/govm/coltx"
88
"github.com/sivaosorg/govm/configx"
9+
"github.com/sivaosorg/govm/ratelimitx"
910
"github.com/sivaosorg/govm/utils"
1011
)
1112

@@ -15,7 +16,8 @@ var Params *keyParams
1516
var Jobs *jobParams
1617

1718
type keyParams struct {
18-
Curl []apix.ApiRequestConfig `json:"curl" yaml:"curl"`
19+
Curl []apix.ApiRequestConfig `json:"curl" yaml:"curl"`
20+
RateLimits []ratelimitx.MultiTenantRateLimitConfig `json:"rate_limit_seekers" yaml:"rate-limit-seekers"`
1921
}
2022

2123
type jobParams struct {

0 commit comments

Comments
 (0)