snapshot of https://github.com/centrifugal/centrifugo.git (master, 2026-09-16; blobs > 131072B omitted)
This commit is contained in:
243
internal/proxy/connect_handler.go
Normal file
243
internal/proxy/connect_handler.go
Normal file
@@ -0,0 +1,243 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/centrifugal/centrifugo/v6/internal/clientstorage"
|
||||
"github.com/centrifugal/centrifugo/v6/internal/config"
|
||||
"github.com/centrifugal/centrifugo/v6/internal/metrics"
|
||||
"github.com/centrifugal/centrifugo/v6/internal/proxyproto"
|
||||
"github.com/centrifugal/centrifugo/v6/internal/subsource"
|
||||
|
||||
"github.com/centrifugal/centrifuge"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// ConnectHandlerConfig ...
|
||||
type ConnectHandlerConfig struct {
|
||||
Proxy ConnectProxy
|
||||
}
|
||||
|
||||
// ConnectHandler ...
|
||||
type ConnectHandler struct {
|
||||
config ConnectHandlerConfig
|
||||
cfgContainer *config.Container
|
||||
summary prometheus.Observer
|
||||
histogram prometheus.Observer
|
||||
errors prometheus.Counter
|
||||
inflight prometheus.Gauge
|
||||
}
|
||||
|
||||
// NewConnectHandler ...
|
||||
func NewConnectHandler(c ConnectHandlerConfig, cfgContainer *config.Container) *ConnectHandler {
|
||||
return &ConnectHandler{
|
||||
config: c,
|
||||
cfgContainer: cfgContainer,
|
||||
summary: metrics.ProxyCallDurationSummary.WithLabelValues(c.Proxy.Protocol(), "connect", "default"),
|
||||
histogram: metrics.ProxyCallDurationHistogram.WithLabelValues(c.Proxy.Protocol(), "connect", "default"),
|
||||
errors: metrics.ProxyCallErrorCount.WithLabelValues(c.Proxy.Protocol(), "connect", "default"),
|
||||
inflight: metrics.ProxyCallInflightRequests.WithLabelValues(c.Proxy.Protocol(), "connect", "default"),
|
||||
}
|
||||
}
|
||||
|
||||
type ConnectExtra struct {
|
||||
}
|
||||
|
||||
type ConnectingHandlerFunc func(context.Context, centrifuge.ConnectEvent) (centrifuge.ConnectReply, ConnectExtra, error)
|
||||
|
||||
// Handle returns connecting handler func.
|
||||
func (h *ConnectHandler) Handle() ConnectingHandlerFunc {
|
||||
return func(ctx context.Context, e centrifuge.ConnectEvent) (centrifuge.ConnectReply, ConnectExtra, error) {
|
||||
started := time.Now()
|
||||
h.inflight.Inc()
|
||||
defer h.inflight.Dec()
|
||||
req := &proxyproto.ConnectRequest{
|
||||
Client: e.ClientID,
|
||||
Protocol: string(e.Transport.Protocol()),
|
||||
Transport: e.Transport.Name(),
|
||||
Encoding: getEncoding(h.config.Proxy.UseBase64()),
|
||||
Name: e.Name,
|
||||
Version: e.Version,
|
||||
Channels: e.Channels,
|
||||
}
|
||||
if !h.config.Proxy.UseBase64() {
|
||||
req.Data = e.Data
|
||||
} else {
|
||||
req.B64Data = base64.StdEncoding.EncodeToString(e.Data)
|
||||
}
|
||||
|
||||
connectRep, err := h.config.Proxy.ProxyConnect(ctx, req)
|
||||
duration := time.Since(started).Seconds()
|
||||
if err != nil {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// Client connection already closed.
|
||||
return centrifuge.ConnectReply{}, ConnectExtra{}, centrifuge.DisconnectConnectionClosed
|
||||
default:
|
||||
}
|
||||
h.summary.Observe(duration)
|
||||
h.histogram.Observe(duration)
|
||||
h.errors.Inc()
|
||||
log.Error().Err(err).Str("client", e.ClientID).Msg("error proxying connect")
|
||||
return centrifuge.ConnectReply{}, ConnectExtra{}, err
|
||||
}
|
||||
h.summary.Observe(duration)
|
||||
h.histogram.Observe(duration)
|
||||
if connectRep.Disconnect != nil {
|
||||
return centrifuge.ConnectReply{}, ConnectExtra{}, proxyproto.DisconnectFromProto(connectRep.Disconnect)
|
||||
}
|
||||
if connectRep.Error != nil {
|
||||
return centrifuge.ConnectReply{}, ConnectExtra{}, proxyproto.ErrorFromProto(connectRep.Error)
|
||||
}
|
||||
|
||||
result := connectRep.Result
|
||||
if result == nil {
|
||||
return centrifuge.ConnectReply{Credentials: nil}, ConnectExtra{}, nil
|
||||
}
|
||||
|
||||
var info []byte
|
||||
if result.B64Info != "" {
|
||||
decodedInfo, err := base64.StdEncoding.DecodeString(result.B64Info)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("client", e.ClientID).Msg("error decoding base64 info")
|
||||
return centrifuge.ConnectReply{}, ConnectExtra{}, centrifuge.ErrorInternal
|
||||
}
|
||||
info = decodedInfo
|
||||
} else {
|
||||
info = result.Info
|
||||
}
|
||||
|
||||
var data []byte
|
||||
if result.B64Data != "" {
|
||||
decodedData, err := base64.StdEncoding.DecodeString(result.B64Data)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("client", e.ClientID).Msg("error decoding base64 data")
|
||||
return centrifuge.ConnectReply{}, ConnectExtra{}, centrifuge.ErrorInternal
|
||||
}
|
||||
data = decodedData
|
||||
} else {
|
||||
data = result.Data
|
||||
}
|
||||
|
||||
reply := centrifuge.ConnectReply{
|
||||
Credentials: ¢rifuge.Credentials{
|
||||
UserID: result.User,
|
||||
ExpireAt: result.ExpireAt,
|
||||
Info: info,
|
||||
},
|
||||
}
|
||||
if len(data) > 0 {
|
||||
reply.Data = data
|
||||
}
|
||||
if len(result.Channels) > 0 {
|
||||
if reply.Subscriptions == nil {
|
||||
reply.Subscriptions = make(map[string]centrifuge.SubscribeOptions, len(result.Channels))
|
||||
}
|
||||
for _, ch := range result.Channels {
|
||||
_, _, chOpts, found, err := h.cfgContainer.ChannelOptions(ch)
|
||||
if err != nil {
|
||||
return centrifuge.ConnectReply{}, ConnectExtra{}, err
|
||||
}
|
||||
if !found {
|
||||
log.Warn().Str("client", e.ClientID).Str("channel", ch).Msg("unknown channel in connect result channels")
|
||||
return centrifuge.ConnectReply{}, ConnectExtra{}, centrifuge.ErrorUnknownChannel
|
||||
}
|
||||
reply.Subscriptions[ch] = centrifuge.SubscribeOptions{
|
||||
EmitPresence: chOpts.Presence,
|
||||
EmitJoinLeave: chOpts.JoinLeave,
|
||||
PushJoinLeave: chOpts.ForcePushJoinLeave,
|
||||
EnableRecovery: chOpts.ForceRecovery,
|
||||
EnablePositioning: chOpts.ForcePositioning,
|
||||
RecoveryMode: chOpts.GetRecoveryMode(),
|
||||
Source: subsource.ConnectProxy,
|
||||
HistoryMetaTTL: chOpts.HistoryMetaTTL.ToDuration(),
|
||||
AllowedDeltaTypes: chOpts.AllowedDeltaTypes,
|
||||
AllowTagsFilter: chOpts.AllowTagsFilter,
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(result.Subs) > 0 {
|
||||
if reply.Subscriptions == nil {
|
||||
reply.Subscriptions = make(map[string]centrifuge.SubscribeOptions, len(result.Subs))
|
||||
}
|
||||
for ch, options := range result.Subs {
|
||||
_, _, chOpts, found, err := h.cfgContainer.ChannelOptions(ch)
|
||||
if err != nil {
|
||||
return centrifuge.ConnectReply{}, ConnectExtra{}, err
|
||||
}
|
||||
if !found {
|
||||
log.Warn().Str("client", e.ClientID).Str("channel", ch).Msg("unknown channel in connect result subs")
|
||||
return centrifuge.ConnectReply{}, ConnectExtra{}, centrifuge.ErrorUnknownChannel
|
||||
}
|
||||
var chInfo []byte
|
||||
if options.B64Info != "" {
|
||||
byteInfo, err := base64.StdEncoding.DecodeString(options.B64Info)
|
||||
if err != nil {
|
||||
return centrifuge.ConnectReply{}, ConnectExtra{}, err
|
||||
}
|
||||
chInfo = byteInfo
|
||||
} else {
|
||||
chInfo = options.Info
|
||||
}
|
||||
var chData []byte
|
||||
if options.B64Data != "" {
|
||||
byteInfo, err := base64.StdEncoding.DecodeString(options.B64Data)
|
||||
if err != nil {
|
||||
return centrifuge.ConnectReply{}, ConnectExtra{}, err
|
||||
}
|
||||
chData = byteInfo
|
||||
} else {
|
||||
chData = options.Data
|
||||
}
|
||||
presence := chOpts.Presence
|
||||
if options.Override != nil && options.Override.Presence != nil {
|
||||
presence = options.Override.Presence.Value
|
||||
}
|
||||
joinLeave := chOpts.JoinLeave
|
||||
if options.Override != nil && options.Override.JoinLeave != nil {
|
||||
joinLeave = options.Override.JoinLeave.Value
|
||||
}
|
||||
pushJoinLeave := chOpts.ForcePushJoinLeave
|
||||
if options.Override != nil && options.Override.ForcePushJoinLeave != nil {
|
||||
pushJoinLeave = options.Override.ForcePushJoinLeave.Value
|
||||
}
|
||||
recovery := chOpts.ForceRecovery
|
||||
if options.Override != nil && options.Override.ForceRecovery != nil {
|
||||
recovery = options.Override.ForceRecovery.Value
|
||||
}
|
||||
positioning := chOpts.ForcePositioning
|
||||
if options.Override != nil && options.Override.ForcePositioning != nil {
|
||||
positioning = options.Override.ForcePositioning.Value
|
||||
}
|
||||
recoveryMode := chOpts.GetRecoveryMode()
|
||||
reply.Subscriptions[ch] = centrifuge.SubscribeOptions{
|
||||
ChannelInfo: chInfo,
|
||||
EmitPresence: presence,
|
||||
EmitJoinLeave: joinLeave,
|
||||
PushJoinLeave: pushJoinLeave,
|
||||
EnableRecovery: recovery,
|
||||
EnablePositioning: positioning,
|
||||
RecoveryMode: recoveryMode,
|
||||
AutoCacheRecover: options.CacheRecover,
|
||||
Data: chData,
|
||||
Source: subsource.ConnectProxy,
|
||||
HistoryMetaTTL: chOpts.HistoryMetaTTL.ToDuration(),
|
||||
AllowedDeltaTypes: chOpts.AllowedDeltaTypes,
|
||||
AllowTagsFilter: chOpts.AllowTagsFilter,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if result.Meta != nil {
|
||||
reply.Storage = map[string]any{
|
||||
clientstorage.KeyMeta: json.RawMessage(result.Meta),
|
||||
}
|
||||
}
|
||||
|
||||
return reply, ConnectExtra{}, nil
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user