Files
upstream-mirror b352a9a30d
Some checks failed
test / Lint (push) Failing after 3s
test / Test with Go 1.26.8 (push) Failing after 2s
snapshot of https://github.com/centrifugal/centrifugo.git (master, 2026-09-16; blobs > 131072B omitted)
2026-09-16 01:17:21 +05:00

534 lines
20 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package app
import (
"fmt"
"strings"
"github.com/centrifugal/centrifugo/v6/internal/client"
"github.com/centrifugal/centrifugo/v6/internal/config"
"github.com/centrifugal/centrifugo/v6/internal/configtypes"
"github.com/centrifugal/centrifugo/v6/internal/proxy"
"github.com/centrifugal/centrifugo/v6/internal/tools"
"github.com/rs/zerolog/log"
)
func buildProxyMap(cfg config.Config) (*client.ProxyMap, bool, error) {
proxyMap := &client.ProxyMap{
ConnectProxy: nil,
RefreshProxy: nil,
RpcProxies: map[string]proxy.RPCProxy{},
PublishProxies: map[string]proxy.PublishProxy{},
SubscribeProxies: map[string]proxy.SubscribeProxy{},
SubRefreshProxies: map[string]proxy.SubRefreshProxy{},
SubscribeStreamProxies: map[string]*proxy.SubscribeStreamProxy{},
MapPublishProxies: map[string]proxy.MapPublishProxy{},
MapRemoveProxies: map[string]proxy.MapRemoveProxy{},
SharedPollRefreshProxies: map[string]*proxy.SharedPollRefreshHandler{},
}
var keepHeadersInContext bool
var keepEmulatedHeadersInContext bool
var err error
var proxyFound bool
if cfg.Client.Proxy.Connect.Enabled {
p := cfg.Client.Proxy.Connect
proxyMap.ConnectProxy, err = proxy.GetConnectProxy("connect", cfg.Client.Proxy.Connect.Proxy)
if err != nil {
return nil, false, fmt.Errorf("error creating connect proxy: %w", err)
}
log.Info().Str("endpoint", tools.RedactedLogURLs(p.Endpoint)[0]).Msg("connect proxy enabled")
if len(p.HttpHeaders) > 0 {
keepHeadersInContext = true
}
if len(p.EmulatedHeaders) > 0 {
keepEmulatedHeadersInContext = true
}
}
if cfg.Client.Proxy.Refresh.Enabled {
p := cfg.Client.Proxy.Refresh
proxyMap.RefreshProxy, err = proxy.GetRefreshProxy("refresh", cfg.Client.Proxy.Refresh.Proxy)
if err != nil {
return nil, false, fmt.Errorf("error creating refresh proxy: %w", err)
}
log.Info().Str("endpoint", tools.RedactedLogURLs(p.Endpoint)[0]).Msg("refresh proxy enabled")
if len(p.HttpHeaders) > 0 {
keepHeadersInContext = true
}
if len(p.EmulatedHeaders) > 0 {
keepEmulatedHeadersInContext = true
}
}
namedProxies := make(map[string]configtypes.Proxy)
for _, p := range cfg.Proxies {
namedProxies[p.Name] = p.Proxy
}
subscribeProxyEnabled := cfg.Channel.WithoutNamespace.SubscribeProxyEnabled
subscribeProxyName := cfg.Channel.WithoutNamespace.SubscribeProxyName
if subscribeProxyEnabled {
var p proxy.Config
if subscribeProxyName == config.DefaultProxyName {
p = cfg.Channel.Proxy.Subscribe
} else {
p, proxyFound = namedProxies[subscribeProxyName]
if !proxyFound {
return nil, false, fmt.Errorf("subscribe proxy not found: %s", subscribeProxyName)
}
}
if _, ok := proxyMap.SubscribeProxies[subscribeProxyName]; !ok {
sp, err := proxy.GetSubscribeProxy(subscribeProxyName, p)
if err != nil {
return nil, false, fmt.Errorf("error creating subscribe proxy %s: %w", subscribeProxyName, err)
}
proxyMap.SubscribeProxies[subscribeProxyName] = sp
}
log.Info().Str("proxy_name", subscribeProxyName).Str("endpoint", tools.RedactedLogURLs(p.Endpoint)[0]).Msg("subscribe proxy enabled for channels without namespace")
if len(p.HttpHeaders) > 0 {
keepHeadersInContext = true
}
if len(p.EmulatedHeaders) > 0 {
keepEmulatedHeadersInContext = true
}
}
publishProxyEnabled := cfg.Channel.WithoutNamespace.PublishProxyEnabled
publishProxyName := cfg.Channel.WithoutNamespace.PublishProxyName
if publishProxyEnabled {
var p proxy.Config
if publishProxyName == config.DefaultProxyName {
p = cfg.Channel.Proxy.Publish
} else {
p, proxyFound = namedProxies[publishProxyName]
if !proxyFound {
return nil, false, fmt.Errorf("publish proxy not found: %s", publishProxyName)
}
}
if _, ok := proxyMap.PublishProxies[publishProxyName]; !ok {
pp, err := proxy.GetPublishProxy(publishProxyName, p)
if err != nil {
return nil, false, fmt.Errorf("error creating publish proxy %s: %w", publishProxyName, err)
}
proxyMap.PublishProxies[publishProxyName] = pp
}
log.Info().Str("proxy_name", publishProxyName).Str("endpoint", tools.RedactedLogURLs(p.Endpoint)[0]).Msg("publish proxy enabled for channels without namespace")
if len(p.HttpHeaders) > 0 {
keepHeadersInContext = true
}
if len(p.EmulatedHeaders) > 0 {
keepEmulatedHeadersInContext = true
}
}
subRefreshProxyEnabled := cfg.Channel.WithoutNamespace.SubRefreshProxyEnabled
subRefreshProxyName := cfg.Channel.WithoutNamespace.SubRefreshProxyName
if subRefreshProxyEnabled {
var p proxy.Config
if subRefreshProxyName == config.DefaultProxyName {
p = cfg.Channel.Proxy.SubRefresh
} else {
p, proxyFound = namedProxies[subRefreshProxyName]
if !proxyFound {
return nil, false, fmt.Errorf("sub refresh proxy not found: %s", subRefreshProxyName)
}
}
if _, ok := proxyMap.SubRefreshProxies[subRefreshProxyName]; !ok {
srp, err := proxy.GetSubRefreshProxy(subRefreshProxyName, p)
if err != nil {
return nil, false, fmt.Errorf("error creating sub refresh proxy %s: %w", subRefreshProxyName, err)
}
proxyMap.SubRefreshProxies[subRefreshProxyName] = srp
}
log.Info().Str("proxy_name", subRefreshProxyName).Str("endpoint", tools.RedactedLogURLs(p.Endpoint)[0]).Msg("sub refresh proxy enabled for channels without namespace")
if len(p.HttpHeaders) > 0 {
keepHeadersInContext = true
}
if len(p.EmulatedHeaders) > 0 {
keepEmulatedHeadersInContext = true
}
}
subscribeStreamProxyEnabled := cfg.Channel.WithoutNamespace.SubscribeStreamProxyEnabled
subscribeStreamProxyName := cfg.Channel.WithoutNamespace.SubscribeStreamProxyName
if subscribeStreamProxyEnabled {
var p proxy.Config
if subscribeStreamProxyName == config.DefaultProxyName {
p = cfg.Channel.Proxy.SubscribeStream
} else {
p, proxyFound = namedProxies[subscribeStreamProxyName]
if !proxyFound {
return nil, false, fmt.Errorf("subscribe stream proxy not found: %s", subscribeStreamProxyName)
}
}
if strings.HasPrefix(p.Endpoint, "http") {
log.Fatal().Str("name", subscribeStreamProxyName).Msg("error creating subscribe stream proxy – only GRPC endpoints supported")
}
if _, ok := proxyMap.SubscribeStreamProxies[subscribeStreamProxyName]; !ok {
sp, err := proxy.NewSubscribeStreamProxy(subscribeStreamProxyName, p)
if err != nil {
return nil, false, fmt.Errorf("error creating subscribe stream proxy %s: %w", subscribeStreamProxyName, err)
}
proxyMap.SubscribeStreamProxies[subscribeStreamProxyName] = sp
}
log.Info().Str("proxy_name", subscribeStreamProxyName).Str("endpoint", tools.RedactedLogURLs(p.Endpoint)[0]).Msg("subscribe stream proxy enabled for channels without namespace")
if len(p.HttpHeaders) > 0 {
keepHeadersInContext = true
}
if len(p.EmulatedHeaders) > 0 {
keepEmulatedHeadersInContext = true
}
}
mapPublishProxyEnabled := cfg.Channel.WithoutNamespace.Map.PublishProxyEnabled
mapPublishProxyName := cfg.Channel.WithoutNamespace.Map.PublishProxyName
if mapPublishProxyEnabled {
var p proxy.Config
if mapPublishProxyName == config.DefaultProxyName {
p = cfg.Channel.Proxy.MapPublish
} else {
p, proxyFound = namedProxies[mapPublishProxyName]
if !proxyFound {
return nil, false, fmt.Errorf("map publish proxy not found: %s", mapPublishProxyName)
}
}
if _, ok := proxyMap.MapPublishProxies[mapPublishProxyName]; !ok {
mpp, err := proxy.GetMapPublishProxy(mapPublishProxyName, p)
if err != nil {
return nil, false, fmt.Errorf("error creating map publish proxy %s: %w", mapPublishProxyName, err)
}
proxyMap.MapPublishProxies[mapPublishProxyName] = mpp
}
log.Info().Str("proxy_name", mapPublishProxyName).Str("endpoint", tools.RedactedLogURLs(p.Endpoint)[0]).Msg("map publish proxy enabled for channels without namespace")
if len(p.HttpHeaders) > 0 {
keepHeadersInContext = true
}
if len(p.EmulatedHeaders) > 0 {
keepEmulatedHeadersInContext = true
}
}
mapRemoveProxyEnabled := cfg.Channel.WithoutNamespace.Map.RemoveProxyEnabled
mapRemoveProxyName := cfg.Channel.WithoutNamespace.Map.RemoveProxyName
if mapRemoveProxyEnabled {
var p proxy.Config
if mapRemoveProxyName == config.DefaultProxyName {
p = cfg.Channel.Proxy.MapRemove
} else {
p, proxyFound = namedProxies[mapRemoveProxyName]
if !proxyFound {
return nil, false, fmt.Errorf("map remove proxy not found: %s", mapRemoveProxyName)
}
}
if _, ok := proxyMap.MapRemoveProxies[mapRemoveProxyName]; !ok {
mrp, err := proxy.GetMapRemoveProxy(mapRemoveProxyName, p)
if err != nil {
return nil, false, fmt.Errorf("error creating map remove proxy %s: %w", mapRemoveProxyName, err)
}
proxyMap.MapRemoveProxies[mapRemoveProxyName] = mrp
}
log.Info().Str("proxy_name", mapRemoveProxyName).Str("endpoint", tools.RedactedLogURLs(p.Endpoint)[0]).Msg("map remove proxy enabled for channels without namespace")
if len(p.HttpHeaders) > 0 {
keepHeadersInContext = true
}
if len(p.EmulatedHeaders) > 0 {
keepEmulatedHeadersInContext = true
}
}
for _, ns := range cfg.Channel.Namespaces {
subscribeProxyEnabled := ns.SubscribeProxyEnabled
subscribeProxyName := ns.SubscribeProxyName
if subscribeProxyEnabled {
var p proxy.Config
if subscribeProxyName == config.DefaultProxyName {
p = cfg.Channel.Proxy.Subscribe
} else {
p, proxyFound = namedProxies[subscribeProxyName]
if !proxyFound {
return nil, false, fmt.Errorf("subscribe proxy not found: %s", subscribeProxyName)
}
}
if _, ok := proxyMap.SubscribeProxies[subscribeProxyName]; !ok {
sp, err := proxy.GetSubscribeProxy(subscribeProxyName, p)
if err != nil {
return nil, false, fmt.Errorf("error creating subscribe proxy %s: %w", subscribeProxyName, err)
}
proxyMap.SubscribeProxies[subscribeProxyName] = sp
}
log.Info().Str("proxy_name", subscribeProxyName).Str("endpoint", tools.RedactedLogURLs(p.Endpoint)[0]).Str("namespace", ns.Name).Msg("subscribe proxy enabled for channels in namespace")
if len(p.HttpHeaders) > 0 {
keepHeadersInContext = true
}
if len(p.EmulatedHeaders) > 0 {
keepEmulatedHeadersInContext = true
}
}
publishProxyEnabled := ns.PublishProxyEnabled
publishProxyName := ns.PublishProxyName
if publishProxyEnabled {
var p proxy.Config
if publishProxyName == config.DefaultProxyName {
p = cfg.Channel.Proxy.Publish
} else {
p, proxyFound = namedProxies[publishProxyName]
if !proxyFound {
return nil, false, fmt.Errorf("publish proxy not found: %s", publishProxyName)
}
}
if _, ok := proxyMap.PublishProxies[publishProxyName]; !ok {
pp, err := proxy.GetPublishProxy(publishProxyName, p)
if err != nil {
return nil, false, fmt.Errorf("error creating publish proxy %s: %w", publishProxyName, err)
}
proxyMap.PublishProxies[publishProxyName] = pp
}
log.Info().Str("proxy_name", publishProxyName).Str("endpoint", tools.RedactedLogURLs(p.Endpoint)[0]).Str("namespace", ns.Name).Msg("publish proxy enabled for channels in namespace")
if len(p.HttpHeaders) > 0 {
keepHeadersInContext = true
}
if len(p.EmulatedHeaders) > 0 {
keepEmulatedHeadersInContext = true
}
}
subRefreshProxyEnabled := ns.SubRefreshProxyEnabled
subRefreshProxyName := ns.SubRefreshProxyName
if subRefreshProxyEnabled {
var p proxy.Config
if subRefreshProxyName == config.DefaultProxyName {
p = cfg.Channel.Proxy.SubRefresh
} else {
p, proxyFound = namedProxies[subRefreshProxyName]
if !proxyFound {
return nil, false, fmt.Errorf("sub refresh proxy not found: %s", subRefreshProxyName)
}
}
if _, ok := proxyMap.SubRefreshProxies[subRefreshProxyName]; !ok {
srp, err := proxy.GetSubRefreshProxy(subRefreshProxyName, p)
if err != nil {
return nil, false, fmt.Errorf("error creating sub refresh proxy %s: %w", subRefreshProxyName, err)
}
proxyMap.SubRefreshProxies[subRefreshProxyName] = srp
}
log.Info().Str("proxy_name", subRefreshProxyName).Str("endpoint", tools.RedactedLogURLs(p.Endpoint)[0]).Str("namespace", ns.Name).Msg("sub refresh proxy enabled for channels in namespace")
if len(p.HttpHeaders) > 0 {
keepHeadersInContext = true
}
if len(p.EmulatedHeaders) > 0 {
keepEmulatedHeadersInContext = true
}
}
subscribeStreamProxyEnabled := ns.SubscribeStreamProxyEnabled
subscribeStreamProxyName := ns.SubscribeStreamProxyName
if subscribeStreamProxyEnabled {
var p proxy.Config
if subscribeStreamProxyName == config.DefaultProxyName {
p = cfg.Channel.Proxy.SubscribeStream
} else {
p, proxyFound = namedProxies[subscribeStreamProxyName]
if !proxyFound {
return nil, false, fmt.Errorf("subscribe stream proxy not found: %s", subscribeStreamProxyName)
}
}
if strings.HasPrefix(p.Endpoint, "http") {
return nil, false, fmt.Errorf("error creating subscribe stream proxy %s only GRPC endpoints supported", subscribeStreamProxyName)
}
if _, ok := proxyMap.SubscribeStreamProxies[subscribeStreamProxyName]; !ok {
sp, err := proxy.NewSubscribeStreamProxy(subscribeStreamProxyName, p)
if err != nil {
return nil, false, fmt.Errorf("error creating subscribe stream proxy %s: %w", subscribeStreamProxyName, err)
}
proxyMap.SubscribeStreamProxies[subscribeStreamProxyName] = sp
}
log.Info().Str("proxy_name", subscribeStreamProxyName).Str("endpoint", tools.RedactedLogURLs(p.Endpoint)[0]).Str("namespace", ns.Name).Msg("subscribe stream proxy enabled for channels in namespace")
if len(p.HttpHeaders) > 0 {
keepHeadersInContext = true
}
if len(p.EmulatedHeaders) > 0 {
keepEmulatedHeadersInContext = true
}
}
mapPublishProxyEnabled := ns.Map.PublishProxyEnabled
mapPublishProxyName := ns.Map.PublishProxyName
if mapPublishProxyEnabled {
var p proxy.Config
if mapPublishProxyName == config.DefaultProxyName {
p = cfg.Channel.Proxy.MapPublish
} else {
p, proxyFound = namedProxies[mapPublishProxyName]
if !proxyFound {
return nil, false, fmt.Errorf("map publish proxy not found: %s", mapPublishProxyName)
}
}
if _, ok := proxyMap.MapPublishProxies[mapPublishProxyName]; !ok {
mpp, err := proxy.GetMapPublishProxy(mapPublishProxyName, p)
if err != nil {
return nil, false, fmt.Errorf("error creating map publish proxy %s: %w", mapPublishProxyName, err)
}
proxyMap.MapPublishProxies[mapPublishProxyName] = mpp
}
log.Info().Str("proxy_name", mapPublishProxyName).Str("endpoint", tools.RedactedLogURLs(p.Endpoint)[0]).Str("namespace", ns.Name).Msg("map publish proxy enabled for channels in namespace")
if len(p.HttpHeaders) > 0 {
keepHeadersInContext = true
}
if len(p.EmulatedHeaders) > 0 {
keepEmulatedHeadersInContext = true
}
}
mapRemoveProxyEnabled := ns.Map.RemoveProxyEnabled
mapRemoveProxyName := ns.Map.RemoveProxyName
if mapRemoveProxyEnabled {
var p proxy.Config
if mapRemoveProxyName == config.DefaultProxyName {
p = cfg.Channel.Proxy.MapRemove
} else {
p, proxyFound = namedProxies[mapRemoveProxyName]
if !proxyFound {
return nil, false, fmt.Errorf("map remove proxy not found: %s", mapRemoveProxyName)
}
}
if _, ok := proxyMap.MapRemoveProxies[mapRemoveProxyName]; !ok {
mrp, err := proxy.GetMapRemoveProxy(mapRemoveProxyName, p)
if err != nil {
return nil, false, fmt.Errorf("error creating map remove proxy %s: %w", mapRemoveProxyName, err)
}
proxyMap.MapRemoveProxies[mapRemoveProxyName] = mrp
}
log.Info().Str("proxy_name", mapRemoveProxyName).Str("endpoint", tools.RedactedLogURLs(p.Endpoint)[0]).Str("namespace", ns.Name).Msg("map remove proxy enabled for channels in namespace")
if len(p.HttpHeaders) > 0 {
keepHeadersInContext = true
}
if len(p.EmulatedHeaders) > 0 {
keepEmulatedHeadersInContext = true
}
}
if ns.SubscriptionType == "shared_poll" {
sharedPollProxyName := ns.SharedPoll.ProxyName
if sharedPollProxyName == "" {
sharedPollProxyName = config.DefaultProxyName
}
var p proxy.Config
if sharedPollProxyName == config.DefaultProxyName {
p = cfg.Channel.Proxy.SharedPollRefresh
} else {
p, proxyFound = namedProxies[sharedPollProxyName]
if !proxyFound {
return nil, false, fmt.Errorf("shared poll refresh proxy not found: %s", sharedPollProxyName)
}
}
if _, ok := proxyMap.SharedPollRefreshProxies[sharedPollProxyName]; !ok {
sp, err := proxy.GetSharedPollRefreshProxy(sharedPollProxyName, p)
if err != nil {
return nil, false, fmt.Errorf("error creating shared poll refresh proxy %s: %w", sharedPollProxyName, err)
}
handler := proxy.NewSharedPollRefreshHandler(proxy.SharedPollRefreshHandlerConfig{
Proxy: sp,
Name: sharedPollProxyName,
})
proxyMap.SharedPollRefreshProxies[sharedPollProxyName] = handler
}
log.Info().Str("proxy_name", sharedPollProxyName).Str("endpoint", tools.RedactedLogURLs(p.Endpoint)[0]).Str("namespace", ns.Name).Msg("shared poll refresh proxy enabled for channels in namespace")
}
}
// Also check without-namespace channels for shared poll proxy.
if cfg.Channel.WithoutNamespace.SubscriptionType == "shared_poll" {
sharedPollProxyName := cfg.Channel.WithoutNamespace.SharedPoll.ProxyName
if sharedPollProxyName == "" {
sharedPollProxyName = config.DefaultProxyName
}
var p proxy.Config
if sharedPollProxyName == config.DefaultProxyName {
p = cfg.Channel.Proxy.SharedPollRefresh
} else {
p, proxyFound = namedProxies[sharedPollProxyName]
if !proxyFound {
return nil, false, fmt.Errorf("shared poll refresh proxy not found: %s", sharedPollProxyName)
}
}
if _, ok := proxyMap.SharedPollRefreshProxies[sharedPollProxyName]; !ok {
sp, err := proxy.GetSharedPollRefreshProxy(sharedPollProxyName, p)
if err != nil {
return nil, false, fmt.Errorf("error creating shared poll refresh proxy %s: %w", sharedPollProxyName, err)
}
handler := proxy.NewSharedPollRefreshHandler(proxy.SharedPollRefreshHandlerConfig{
Proxy: sp,
Name: sharedPollProxyName,
})
proxyMap.SharedPollRefreshProxies[sharedPollProxyName] = handler
}
log.Info().Str("proxy_name", sharedPollProxyName).Str("endpoint", tools.RedactedLogURLs(p.Endpoint)[0]).Msg("shared poll refresh proxy enabled for channels without namespace")
}
rpcProxyEnabled := cfg.RPC.WithoutNamespace.ProxyEnabled
rpcProxyName := cfg.RPC.WithoutNamespace.ProxyName
if rpcProxyEnabled {
var p proxy.Config
if rpcProxyName == config.DefaultProxyName {
p = cfg.RPC.Proxy
} else {
p, proxyFound = namedProxies[rpcProxyName]
if !proxyFound {
return nil, false, fmt.Errorf("rpc proxy not found: %s", rpcProxyName)
}
}
if _, ok := proxyMap.RpcProxies[rpcProxyName]; !ok {
rp, err := proxy.GetRpcProxy(rpcProxyName, p)
if err != nil {
return nil, false, fmt.Errorf("error creating rpc proxy %s: %w", rpcProxyName, err)
}
proxyMap.RpcProxies[rpcProxyName] = rp
}
log.Info().Str("proxy_name", rpcProxyName).Str("endpoint", tools.RedactedLogURLs(p.Endpoint)[0]).Msg("RPC proxy enabled for methods without namespace")
if len(p.HttpHeaders) > 0 {
keepHeadersInContext = true
}
if len(p.EmulatedHeaders) > 0 {
keepEmulatedHeadersInContext = true
}
}
for _, ns := range cfg.RPC.Namespaces {
rpcProxyEnabled := ns.ProxyEnabled
rpcProxyName := ns.ProxyName
if rpcProxyEnabled {
var p proxy.Config
if rpcProxyName == config.DefaultProxyName {
p = cfg.RPC.Proxy
} else {
p, proxyFound = namedProxies[rpcProxyName]
if !proxyFound {
return nil, false, fmt.Errorf("rpc proxy not found: %s", rpcProxyName)
}
}
if _, ok := proxyMap.RpcProxies[rpcProxyName]; !ok {
rp, err := proxy.GetRpcProxy(rpcProxyName, p)
if err != nil {
return nil, false, fmt.Errorf("error creating rpc proxy %s: %w", rpcProxyName, err)
}
proxyMap.RpcProxies[rpcProxyName] = rp
}
log.Info().Str("proxy_name", rpcProxyName).Str("endpoint", tools.RedactedLogURLs(p.Endpoint)[0]).Str("namespace", ns.Name).Msg("RPC proxy enabled for namespace")
if len(p.HttpHeaders) > 0 {
keepHeadersInContext = true
}
if len(p.EmulatedHeaders) > 0 {
keepEmulatedHeadersInContext = true
}
}
}
proxyMap.UsesEmulatedHeaders = keepEmulatedHeadersInContext
return proxyMap, keepHeadersInContext, nil
}