Skip to content

Commit dba55cf

Browse files
committed
rework proxy, headers emulation
1 parent 50ff1e0 commit dba55cf

39 files changed

+621
-448
lines changed

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ go 1.23.0
55
require (
66
github.com/FZambia/eagle v0.1.0
77
github.com/FZambia/statik v0.1.2-0.20180217151304-b9f012bb2a1b
8-
github.com/centrifugal/centrifuge v0.33.5-0.20241104073442-b695b2eb669d
9-
github.com/centrifugal/protocol v0.13.5-0.20241030080628-ab8a125839c1
8+
github.com/centrifugal/centrifuge v0.33.5-0.20241111162802-ddd7cc1e7267
9+
github.com/centrifugal/protocol v0.13.5-0.20241111155425-6c360178091e
1010
github.com/cristalhq/jwt/v5 v5.4.0
1111
github.com/go-viper/mapstructure/v2 v2.1.0
1212
github.com/gobwas/glob v0.2.3

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
66
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
77
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
88
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
9-
github.com/centrifugal/centrifuge v0.33.5-0.20241104073442-b695b2eb669d h1:k53DsmeNfhw7KbM+T8qeIOL23vI3ewLK1vmv+IYb04U=
10-
github.com/centrifugal/centrifuge v0.33.5-0.20241104073442-b695b2eb669d/go.mod h1:yvzNn5hq/bFBpoXQwM8HbU481pAXQkyP2tzvJgFsiN8=
11-
github.com/centrifugal/protocol v0.13.5-0.20241030080628-ab8a125839c1 h1:itkwGpNchf1X8djN+S90koPptcnxzRGh2atzdw6ZZtc=
12-
github.com/centrifugal/protocol v0.13.5-0.20241030080628-ab8a125839c1/go.mod h1:7V5vI30VcoxJe4UD87xi7bOsvI0bmEhvbQuMjrFM2L4=
9+
github.com/centrifugal/centrifuge v0.33.5-0.20241111162802-ddd7cc1e7267 h1:qCDl370NqiN1XsVnpaHnnH3ixLM0oNBujHIphoI/4/Q=
10+
github.com/centrifugal/centrifuge v0.33.5-0.20241111162802-ddd7cc1e7267/go.mod h1:enLQkNNo05bv/a2fKWHS2IyhrE91TQJghpygTKtqfmM=
11+
github.com/centrifugal/protocol v0.13.5-0.20241111155425-6c360178091e h1:+GbuEwJybDuHz6e8S17t/f0I4aTDnZjk37c0aGNFbwc=
12+
github.com/centrifugal/protocol v0.13.5-0.20241111155425-6c360178091e/go.mod h1:7V5vI30VcoxJe4UD87xi7bOsvI0bmEhvbQuMjrFM2L4=
1313
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
1414
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
1515
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=

internal/client/handler.go

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"errors"
77
"unicode"
88

9+
"github.com/centrifugal/centrifugo/v5/internal/clientcontext"
910
"github.com/centrifugal/centrifugo/v5/internal/clientstorage"
1011
"github.com/centrifugal/centrifugo/v5/internal/config"
1112
"github.com/centrifugal/centrifugo/v5/internal/configtypes"
@@ -31,7 +32,7 @@ type ProxyMap struct {
3132
SubscribeStreamProxies map[string]*proxy.SubscribeStreamProxy
3233
}
3334

34-
// Handler ...
35+
// Handler for client connections.
3536
type Handler struct {
3637
node *centrifuge.Node
3738
cfgContainer *config.Container
@@ -41,7 +42,7 @@ type Handler struct {
4142
rpcExtension map[string]RPCExtensionFunc
4243
}
4344

44-
// NewHandler ...
45+
// NewHandler creates new Handler.
4546
func NewHandler(
4647
node *centrifuge.Node,
4748
cfgContainer *config.Container,
@@ -337,7 +338,7 @@ func (h *Handler) OnClientConnecting(
337338

338339
processClientChannels = true
339340
} else if connectProxyHandler != nil {
340-
connectReply, _, err := connectProxyHandler(ctx, e)
341+
connectReply, _, err := connectProxyHandler(clientcontext.SetEmulatedHeadersToContext(ctx, e.Headers), e)
341342
if err != nil {
342343
return centrifuge.ConnectReply{}, err
343344
}
@@ -414,7 +415,7 @@ func (h *Handler) OnClientConnecting(
414415
return centrifuge.ConnectReply{}, centrifuge.ErrorInternal
415416
}
416417

417-
if !isPrivate && chOpts.SubscribeProxyName == "" && validChannelName {
418+
if !isPrivate && !chOpts.SubscribeProxyEnabled && validChannelName {
418419
if isUserLimited && chOpts.UserLimitedChannels && (userID != "" && h.cfgContainer.UserAllowed(ch, userID)) {
419420
channelOk = true
420421
} else if chOpts.SubscribeForClient && (userID != "" || chOpts.SubscribeForAnonymous) {
@@ -450,6 +451,13 @@ func (h *Handler) OnClientConnecting(
450451
Data: data,
451452
ClientSideRefresh: !refreshProxyEnabled,
452453
}
454+
if len(e.Headers) > 0 {
455+
if newCtx != nil {
456+
newCtx = clientcontext.SetEmulatedHeadersToContext(newCtx, e.Headers)
457+
} else {
458+
newCtx = clientcontext.SetEmulatedHeadersToContext(ctx, e.Headers)
459+
}
460+
}
453461
if newCtx != nil {
454462
finalReply.Context = newCtx
455463
}
@@ -695,17 +703,17 @@ func (h *Handler) OnSubscribe(c Client, e centrifuge.SubscribeEvent, subscribePr
695703
} else if isUserLimitedChannel && h.cfgContainer.UserAllowed(e.Channel, c.UserID()) {
696704
allowed = true
697705
options.Source = subsource.UserLimited
698-
} else if (chOpts.SubscribeProxyName != "") && !isUserLimitedChannel {
706+
} else if (chOpts.SubscribeProxyEnabled) && !isUserLimitedChannel {
699707
if subscribeProxyHandler == nil {
700708
h.node.Log(centrifuge.NewLogEntry(centrifuge.LogLevelInfo, "subscribe proxy not enabled", map[string]any{"channel": e.Channel, "user": c.UserID(), "client": c.ID()}))
701709
return centrifuge.SubscribeReply{}, SubscribeExtra{}, centrifuge.ErrorNotAvailable
702710
}
703711
r, _, err := subscribeProxyHandler(c, e, chOpts, getPerCallData(c))
704-
if chOpts.SubRefreshProxyName != "" {
712+
if chOpts.SubRefreshProxyEnabled {
705713
r.ClientSideRefresh = false
706714
}
707715
return r, SubscribeExtra{}, err
708-
} else if (chOpts.SubscribeStreamProxyName != "") && !isUserLimitedChannel {
716+
} else if (chOpts.SubscribeStreamProxyEnabled) && !isUserLimitedChannel {
709717
if subscribeStreamHandlerFunc == nil {
710718
h.node.Log(centrifuge.NewLogEntry(centrifuge.LogLevelInfo, "stream proxy not enabled", map[string]any{"channel": e.Channel, "user": c.UserID(), "client": c.ID()}))
711719
return centrifuge.SubscribeReply{}, SubscribeExtra{}, centrifuge.ErrorNotAvailable
@@ -717,7 +725,7 @@ func (h *Handler) OnSubscribe(c Client, e centrifuge.SubscribeEvent, subscribePr
717725
storage["stream_publisher_"+e.Channel] = publishFunc
718726
release(storage)
719727
}
720-
if chOpts.SubRefreshProxyName != "" {
728+
if chOpts.SubRefreshProxyEnabled {
721729
r.ClientSideRefresh = false
722730
}
723731
return r, SubscribeExtra{}, err
@@ -748,7 +756,7 @@ func (h *Handler) OnSubscribe(c Client, e centrifuge.SubscribeEvent, subscribePr
748756

749757
return centrifuge.SubscribeReply{
750758
Options: options,
751-
ClientSideRefresh: chOpts.SubRefreshProxyName == "",
759+
ClientSideRefresh: !chOpts.SubRefreshProxyEnabled,
752760
}, SubscribeExtra{}, nil
753761
}
754762

@@ -771,13 +779,13 @@ func (h *Handler) OnPublish(c Client, e centrifuge.PublishEvent, publishProxyHan
771779

772780
var allowed bool
773781

774-
if chOpts.PublishProxyName != "" {
782+
if chOpts.PublishProxyEnabled {
775783
if publishProxyHandler == nil {
776784
h.node.Log(centrifuge.NewLogEntry(centrifuge.LogLevelInfo, "publish proxy not enabled", map[string]any{"channel": e.Channel, "user": c.UserID(), "client": c.ID()}))
777785
return centrifuge.PublishReply{}, centrifuge.ErrorNotAvailable
778786
}
779787
return publishProxyHandler(c, e, chOpts, getPerCallData(c))
780-
} else if chOpts.SubscribeStreamProxyName != "" {
788+
} else if chOpts.SubscribeStreamProxyEnabled {
781789
if !chOpts.SubscribeStreamBidirectional {
782790
return centrifuge.PublishReply{}, centrifuge.ErrorNotAvailable
783791
}

internal/client/handler_test.go

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,9 +1068,9 @@ func TestClientOnSubscribe_UserLimitedChannelDoesNotCallProxy(t *testing.T) {
10681068
defer func() { _ = node.Shutdown(context.Background()) }()
10691069

10701070
cfg := config.DefaultConfig()
1071-
cfg.UnifiedProxy.SubscribeEndpoint = "http://localhost:8080"
1071+
cfg.Channel.Proxy.Subscribe.Endpoint = "http://localhost:8080"
10721072
cfg.Channel.WithoutNamespace.UserLimitedChannels = true
1073-
cfg.Channel.WithoutNamespace.SubscribeProxyName = "unified"
1073+
cfg.Channel.WithoutNamespace.SubscribeProxyEnabled = true
10741074
cfgContainer, err := config.NewContainer(cfg)
10751075
require.NoError(t, err)
10761076

@@ -1147,3 +1147,51 @@ func TestClientOnSubscribe_UserLimitedChannelNotAllowedForAnotherUser(t *testing
11471147
}, proxyFunc, nil)
11481148
require.ErrorIs(t, err, centrifuge.ErrorPermissionDenied)
11491149
}
1150+
1151+
func TestClientOnSubscribe_SubRefreshProxy(t *testing.T) {
1152+
node := tools.NodeWithMemoryEngineNoHandlers()
1153+
defer func() { _ = node.Shutdown(context.Background()) }()
1154+
1155+
cfg := config.DefaultConfig()
1156+
cfg.Channel.Proxy.Subscribe.Endpoint = "http://localhost:8080"
1157+
cfg.Channel.WithoutNamespace.SubscribeProxyEnabled = true
1158+
cfgContainer, err := config.NewContainer(cfg)
1159+
require.NoError(t, err)
1160+
1161+
h := NewHandler(node, cfgContainer, nil, nil, &ProxyMap{})
1162+
1163+
numProxyCalls := 0
1164+
1165+
proxyFunc := func(c proxy.Client, e centrifuge.SubscribeEvent, chOpts configtypes.ChannelOptions, pcd proxy.PerCallData) (centrifuge.SubscribeReply, proxy.SubscribeExtra, error) {
1166+
numProxyCalls++
1167+
return centrifuge.SubscribeReply{
1168+
ClientSideRefresh: !chOpts.SubRefreshProxyEnabled,
1169+
}, proxy.SubscribeExtra{}, nil
1170+
}
1171+
1172+
reply, _, err := h.OnSubscribe(&tools.TestClientMock{
1173+
UserIDFunc: func() string {
1174+
return "42"
1175+
},
1176+
}, centrifuge.SubscribeEvent{
1177+
Channel: "user",
1178+
}, proxyFunc, nil)
1179+
require.NoError(t, err)
1180+
require.Equal(t, 1, numProxyCalls)
1181+
require.True(t, reply.ClientSideRefresh)
1182+
1183+
cfg.Channel.WithoutNamespace.SubRefreshProxyEnabled = true
1184+
cfg.Channel.Proxy.SubRefresh.Endpoint = "https://example.com"
1185+
err = cfgContainer.Reload(cfg)
1186+
require.NoError(t, err)
1187+
reply, _, err = h.OnSubscribe(&tools.TestClientMock{
1188+
UserIDFunc: func() string {
1189+
return "42"
1190+
},
1191+
}, centrifuge.SubscribeEvent{
1192+
Channel: "user",
1193+
}, proxyFunc, nil)
1194+
require.NoError(t, err)
1195+
require.Equal(t, 2, numProxyCalls)
1196+
require.False(t, reply.ClientSideRefresh)
1197+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package clientcontext
2+
3+
import (
4+
"context"
5+
)
6+
7+
type emulatedHeadersKey struct{}
8+
9+
// GetEmulatedHeadersFromContext returns emulated headers from context.
10+
func GetEmulatedHeadersFromContext(ctx context.Context) (map[string]string, bool) {
11+
if val := ctx.Value(emulatedHeadersKey{}); val != nil {
12+
values, ok := val.(map[string]string)
13+
return values, ok
14+
}
15+
return nil, false
16+
}
17+
18+
// SetEmulatedHeadersToContext sets header map to context.
19+
func SetEmulatedHeadersToContext(ctx context.Context, h map[string]string) context.Context {
20+
if len(h) == 0 {
21+
return ctx
22+
}
23+
return context.WithValue(ctx, emulatedHeadersKey{}, h)
24+
}

internal/config/config.go

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -67,17 +67,15 @@ type Config struct {
6767
Channel configtypes.Channel `mapstructure:"channel" json:"channel" envconfig:"channel" toml:"channel" yaml:"channel"`
6868
// RPC is a configuration for client RPC calls.
6969
RPC configtypes.RPC `mapstructure:"rpc" json:"rpc" envconfig:"rpc" toml:"rpc" yaml:"rpc"`
70+
// Proxies is an array of proxies with custom names for the more granular control of channel-related events
71+
// in different channel namespaces.
72+
Proxies configtypes.NamedProxies `mapstructure:"proxies" default:"[]" json:"proxies" envconfig:"proxies" yaml:"proxies" toml:"proxies"`
7073

7174
// HttpAPI is a configuration for HTTP server API. It's enabled by default.
7275
HttpAPI configtypes.HttpAPI `mapstructure:"http_api" json:"http_api" envconfig:"http_api" toml:"http_api" yaml:"http_api"`
7376
// GrpcAPI is a configuration for gRPC server API. It's disabled by default.
7477
GrpcAPI configtypes.GrpcAPI `mapstructure:"grpc_api" json:"grpc_api" envconfig:"grpc_api" toml:"grpc_api" yaml:"grpc_api"`
7578

76-
// UnifiedProxy is a helper configuration for events proxy. It can be referenced using UnifiedProxyName name.
77-
UnifiedProxy configtypes.UnifiedProxy `mapstructure:"unified_proxy" json:"unified_proxy" envconfig:"unified_proxy" toml:"unified_proxy" yaml:"unified_proxy"`
78-
// Proxies is a configuration for granular events proxies. See also UnifiedProxy.
79-
Proxies configtypes.Proxies `mapstructure:"proxies" default:"[]" json:"proxies" envconfig:"proxies" toml:"proxies" yaml:"proxies"`
80-
8179
// Consumers is a configuration for message queue consumers. For example, Centrifugo can consume
8280
// messages from PostgreSQL transactional outbox table, or from Kafka topics.
8381
Consumers configtypes.Consumers `mapstructure:"consumers" default:"[]" json:"consumers" envconfig:"consumers" toml:"consumers" yaml:"consumers"`
@@ -242,7 +240,7 @@ func GetConfig(cmd *cobra.Command, configFile string) (Config, Meta, error) {
242240
for i, item := range conf.Proxies {
243241
varInfo, err = envconfig.Process("CENTRIFUGO_PROXIES_"+item.Name, &item)
244242
if err != nil {
245-
return Config{}, Meta{}, fmt.Errorf("error processing env proxies: %w", err)
243+
return Config{}, Meta{}, fmt.Errorf("error processing env named proxies: %w", err)
246244
}
247245
conf.Proxies[i] = item
248246
extendKnownEnvVars(knownEnvVars, varInfo)
@@ -257,15 +255,6 @@ func GetConfig(cmd *cobra.Command, configFile string) (Config, Meta, error) {
257255
extendKnownEnvVars(knownEnvVars, varInfo)
258256
}
259257

260-
for i, header := range conf.UnifiedProxy.HttpHeaders {
261-
conf.UnifiedProxy.HttpHeaders[i] = strings.ToLower(header)
262-
}
263-
for i, proxy := range conf.Proxies {
264-
for j, header := range proxy.HttpHeaders {
265-
conf.Proxies[i].HttpHeaders[j] = strings.ToLower(header)
266-
}
267-
}
268-
269258
meta.UnknownKeys = findUnknownKeys(v.AllSettings(), conf, "")
270259
meta.UnknownEnvs = checkEnvironmentVars(knownEnvVars)
271260
meta.KnownEnvVars = knownEnvVars

internal/config/config_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ func TestConfigEnvVars(t *testing.T) {
5858
_ = os.Setenv("CENTRIFUGO_CONSUMERS_KAFKA_KAFKA_TLS_ENABLED", "false")
5959
_ = os.Setenv("CENTRIFUGO_UNKNOWN_ENV", "1")
6060
_ = os.Setenv("CENTRIFUGO_CHANNEL_NAMESPACES", `[{"name": "env"}]`)
61-
_ = os.Setenv("CENTRIFUGO_UNIFIED_PROXY_HTTP_STATIC_HEADERS", `{"key": "value"}`)
61+
_ = os.Setenv("CENTRIFUGO_CLIENT_PROXY_CONNECT_HTTP_STATIC_HEADERS", `{"key": "value"}`)
6262
_ = os.Setenv("CENTRIFUGO_WEBSOCKET_WRITE_TIMEOUT", `300ms`)
6363
_ = os.Setenv("CENTRIFUGO_PROXIES", `[]`)
6464
defer func() {
@@ -67,7 +67,7 @@ func TestConfigEnvVars(t *testing.T) {
6767
_ = os.Unsetenv("CENTRIFUGO_CLIENT_ALLOWED_ORIGINS")
6868
_ = os.Unsetenv("CENTRIFUGO_CLIENT_TOKEN_JWKS_PUBLIC_ENDPOINT")
6969
_ = os.Unsetenv("CENTRIFUGO_CHANNEL_NAMESPACES")
70-
_ = os.Unsetenv("CENTRIFUGO_UNIFIED_PROXY_HTTP_STATIC_HEADERS")
70+
_ = os.Unsetenv("CENTRIFUGO_CLIENT_PROXY_CONNECT_HTTP_STATIC_HEADERS")
7171
_ = os.Unsetenv("CENTRIFUGO_WEBSOCKET_WRITE_TIMEOUT")
7272
_ = os.Unsetenv("CENTRIFUGO_PROXIES")
7373
}()
@@ -81,7 +81,7 @@ func TestConfigEnvVars(t *testing.T) {
8181
require.Len(t, meta.UnknownEnvs, 1)
8282
require.Len(t, meta.UnknownKeys, 0)
8383
require.Contains(t, meta.UnknownEnvs, "CENTRIFUGO_UNKNOWN_ENV")
84-
require.Equal(t, configtypes.MapStringString(map[string]string{"key": "value"}), conf.UnifiedProxy.HTTP.StaticHeaders)
84+
require.Equal(t, configtypes.MapStringString(map[string]string{"key": "value"}), conf.Client.Proxy.Connect.HTTP.StaticHeaders)
8585
require.Equal(t, configtypes.Duration(300*time.Millisecond), conf.WebSocket.WriteTimeout)
8686
require.Len(t, conf.Proxies, 0)
8787
}

internal/config/const.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package config
22

33
const (
4-
UnifiedProxyName = "unified"
4+
DefaultProxyName = "default"
55
TransportErrorMode = "transport"
66
)

internal/config/testdata/config.json

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,16 @@
77
"token": {
88
"jwks_public_endpoint": "https://example.com/jwks"
99
},
10-
"ping_interval": "12s"
10+
"ping_interval": "12s",
11+
"proxy": {
12+
"connect": {
13+
"http": {
14+
"static_headers": {
15+
"x": "y"
16+
}
17+
}
18+
}
19+
}
1120
},
1221
"channel": {
1322
"without_namespace": {
@@ -45,13 +54,6 @@
4554
}
4655
}
4756
],
48-
"unified_proxy": {
49-
"http": {
50-
"static_headers": {
51-
"x": "y"
52-
}
53-
}
54-
},
5557
"websocket": {
5658
"write_timeout": "2s"
5759
}

0 commit comments

Comments
 (0)