Skip to content

Commit 0eeab81

Browse files
authored
Add client_connection_rate_limit option (#601)
1 parent 2a24c40 commit 0eeab81

File tree

6 files changed

+56
-1
lines changed

6 files changed

+56
-1
lines changed

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ require (
3030
go.uber.org/automaxprocs v1.5.1
3131
golang.org/x/crypto v0.4.0
3232
golang.org/x/sync v0.1.0
33+
golang.org/x/time v0.3.0
3334
google.golang.org/grpc v1.51.0
3435
google.golang.org/protobuf v1.28.1
3536
)

go.sum

+2-1
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
326326
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
327327
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
328328
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
329-
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 h1:GZokNIeuVkl3aZHJchRrr13WCsols02MLUcz1U9is6M=
329+
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
330+
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
330331
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
331332
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
332333
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

internal/middleware/connlimit.go

+13
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99

1010
"github.com/centrifugal/centrifuge"
1111
"github.com/prometheus/client_golang/prometheus"
12+
"golang.org/x/time/rate"
1213
)
1314

1415
var (
@@ -29,7 +30,12 @@ func init() {
2930
}
3031

3132
func ConnLimit(node *centrifuge.Node, ruleContainer *rule.Container, h http.Handler) http.Handler {
33+
rl := connectionRateLimiter(ruleContainer.Config().ClientConnectionRateLimit)
3234
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
35+
if rl != nil && !rl.Allow() {
36+
w.WriteHeader(http.StatusServiceUnavailable)
37+
return
38+
}
3339
connLimit := ruleContainer.Config().ClientConnectionLimit
3440
if connLimit > 0 && node.Hub().NumClients() >= connLimit {
3541
connLimitReached.Inc()
@@ -45,3 +51,10 @@ func ConnLimit(node *centrifuge.Node, ruleContainer *rule.Container, h http.Hand
4551
h.ServeHTTP(w, r)
4652
})
4753
}
54+
55+
func connectionRateLimiter(connRateLimit int) *rate.Limiter {
56+
if connRateLimit > 0 {
57+
return rate.NewLimiter(rate.Every(time.Second), connRateLimit)
58+
}
59+
return nil
60+
}

internal/middleware/connlimit_test.go

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package middleware
2+
3+
import (
4+
"context"
5+
"net/http"
6+
"net/http/httptest"
7+
"testing"
8+
9+
"github.com/centrifugal/centrifugo/v4/internal/rule"
10+
"github.com/centrifugal/centrifugo/v4/internal/tools"
11+
12+
"github.com/stretchr/testify/require"
13+
)
14+
15+
func TestConnLimit_ConnectionRate(t *testing.T) {
16+
node := tools.NodeWithMemoryEngineNoHandlers()
17+
defer func() { _ = node.Shutdown(context.Background()) }()
18+
19+
ruleConfig, err := rule.NewContainer(rule.Config{
20+
ClientConnectionRateLimit: 10,
21+
})
22+
require.NoError(t, err)
23+
24+
ts := httptest.NewServer(ConnLimit(node, ruleConfig, testHandler()))
25+
defer ts.Close()
26+
27+
for i := 0; i < 20; i++ {
28+
res, err := http.Post(ts.URL, "application/json", nil)
29+
require.NoError(t, err)
30+
if res.StatusCode == http.StatusServiceUnavailable {
31+
require.True(t, i >= 10)
32+
return
33+
}
34+
}
35+
require.Fail(t, "no rate limit hit upon sending 10 requests to a server")
36+
}

internal/rule/rule.go

+3
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ type Config struct {
7272
// ClientConnectionLimit sets the maximum number of concurrent clients a single Centrifugo
7373
// node will accept.
7474
ClientConnectionLimit int
75+
// ClientConnectionRateLimit sets the maximum number of new connections a single Centrifugo
76+
// node will accept per second.
77+
ClientConnectionRateLimit int
7578
}
7679

7780
// DefaultConfig has default config options.

main.go

+1
Original file line numberDiff line numberDiff line change
@@ -1413,6 +1413,7 @@ func ruleConfig() rule.Config {
14131413
cfg.RpcProxyName = v.GetString("rpc_proxy_name")
14141414
cfg.RpcNamespaces = rpcNamespacesFromConfig(v)
14151415
cfg.ClientConnectionLimit = v.GetInt("client_connection_limit")
1416+
cfg.ClientConnectionRateLimit = v.GetInt("client_connection_rate_limit")
14161417
return cfg
14171418
}
14181419

0 commit comments

Comments
 (0)