Files
upstream-centrifugo/internal/proxy/subscribe_handler.go
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

201 lines
6.6 KiB
Go

package proxy
import (
"encoding/base64"
"time"
"github.com/centrifugal/centrifugo/v6/internal/configtypes"
"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"
)
// SubscribeHandlerConfig ...
type SubscribeHandlerConfig struct {
Proxies map[string]SubscribeProxy
}
// SubscribeHandler ...
type SubscribeHandler struct {
config SubscribeHandlerConfig
summary map[string]prometheus.Observer
histogram map[string]prometheus.Observer
errors map[string]prometheus.Counter
inflight map[string]prometheus.Gauge
}
// NewSubscribeHandler ...
func NewSubscribeHandler(c SubscribeHandlerConfig) *SubscribeHandler {
h := &SubscribeHandler{
config: c,
}
summary := map[string]prometheus.Observer{}
histogram := map[string]prometheus.Observer{}
errors := map[string]prometheus.Counter{}
inflight := map[string]prometheus.Gauge{}
for name, p := range c.Proxies {
summary[name] = metrics.ProxyCallDurationSummary.WithLabelValues(p.Protocol(), "subscribe", name)
histogram[name] = metrics.ProxyCallDurationHistogram.WithLabelValues(p.Protocol(), "subscribe", name)
errors[name] = metrics.ProxyCallErrorCount.WithLabelValues(p.Protocol(), "subscribe", name)
inflight[name] = metrics.ProxyCallInflightRequests.WithLabelValues(p.Protocol(), "subscribe", name)
}
h.summary = summary
h.histogram = histogram
h.errors = errors
h.inflight = inflight
return h
}
type SubscribeExtra struct {
}
// SubscribeHandlerFunc ...
type SubscribeHandlerFunc func(Client, centrifuge.SubscribeEvent, configtypes.ChannelOptions, PerCallData) (centrifuge.SubscribeReply, SubscribeExtra, error)
// Handle Subscribe.
func (h *SubscribeHandler) Handle() SubscribeHandlerFunc {
return func(client Client, e centrifuge.SubscribeEvent, chOpts configtypes.ChannelOptions, pcd PerCallData) (centrifuge.SubscribeReply, SubscribeExtra, error) {
started := time.Now()
var p SubscribeProxy
var summary prometheus.Observer
var histogram prometheus.Observer
var errors prometheus.Counter
proxyEnabled := chOpts.SubscribeProxyEnabled
proxyName := chOpts.SubscribeProxyName
if !proxyEnabled {
log.Info().Str("channel", e.Channel).Msg("subscribe proxy not enabled for a channel")
return centrifuge.SubscribeReply{}, SubscribeExtra{}, centrifuge.ErrorNotAvailable
}
p = h.config.Proxies[proxyName]
summary = h.summary[proxyName]
histogram = h.histogram[proxyName]
errors = h.errors[proxyName]
inflight := h.inflight[proxyName]
inflight.Inc()
defer inflight.Dec()
req := &proxyproto.SubscribeRequest{
Client: client.ID(),
Protocol: string(client.Transport().Protocol()),
Transport: client.Transport().Name(),
Encoding: getEncoding(p.UseBase64()),
User: client.UserID(),
Channel: e.Channel,
Token: e.Token,
}
if !p.UseBase64() {
req.Data = e.Data
} else {
req.B64Data = base64.StdEncoding.EncodeToString(e.Data)
}
if p.IncludeMeta() && pcd.Meta != nil {
req.Meta = proxyproto.Raw(pcd.Meta)
}
subscribeRep, err := p.ProxySubscribe(client.Context(), req)
duration := time.Since(started).Seconds()
if err != nil {
select {
case <-client.Context().Done():
// Client connection already closed.
return centrifuge.SubscribeReply{}, SubscribeExtra{}, centrifuge.DisconnectConnectionClosed
default:
}
summary.Observe(duration)
histogram.Observe(duration)
errors.Inc()
log.Error().Err(err).Str("client", client.ID()).Str("channel", e.Channel).Msg("error proxying subscribe")
return centrifuge.SubscribeReply{}, SubscribeExtra{}, err
}
summary.Observe(duration)
histogram.Observe(duration)
if subscribeRep.Disconnect != nil {
return centrifuge.SubscribeReply{}, SubscribeExtra{}, proxyproto.DisconnectFromProto(subscribeRep.Disconnect)
}
if subscribeRep.Error != nil {
return centrifuge.SubscribeReply{}, SubscribeExtra{}, proxyproto.ErrorFromProto(subscribeRep.Error)
}
presence := chOpts.Presence
joinLeave := chOpts.JoinLeave
pushJoinLeave := chOpts.ForcePushJoinLeave
recovery := chOpts.ForceRecovery
positioning := chOpts.ForcePositioning
recoveryMode := chOpts.GetRecoveryMode()
var info []byte
var data []byte
var expireAt int64
var extra SubscribeExtra
if subscribeRep.Result != nil {
if subscribeRep.Result.B64Info != "" {
decodedInfo, err := base64.StdEncoding.DecodeString(subscribeRep.Result.B64Info)
if err != nil {
log.Error().Err(err).Str("client", client.ID()).Msg("error decoding base64 info")
return centrifuge.SubscribeReply{}, SubscribeExtra{}, centrifuge.ErrorInternal
}
info = decodedInfo
} else {
info = subscribeRep.Result.Info
}
if subscribeRep.Result.B64Data != "" {
decodedData, err := base64.StdEncoding.DecodeString(subscribeRep.Result.B64Data)
if err != nil {
log.Error().Err(err).Str("client", client.ID()).Msg("error decoding base64 data")
return centrifuge.SubscribeReply{}, SubscribeExtra{}, centrifuge.ErrorInternal
}
data = decodedData
} else {
data = subscribeRep.Result.Data
}
result := subscribeRep.Result
if result.Override != nil && result.Override.Presence != nil {
presence = result.Override.Presence.Value
}
if result.Override != nil && result.Override.JoinLeave != nil {
joinLeave = result.Override.JoinLeave.Value
}
if result.Override != nil && result.Override.ForcePushJoinLeave != nil {
pushJoinLeave = result.Override.ForcePushJoinLeave.Value
}
if result.Override != nil && result.Override.ForceRecovery != nil {
recovery = result.Override.ForceRecovery.Value
}
if result.Override != nil && result.Override.ForcePositioning != nil {
positioning = result.Override.ForcePositioning.Value
}
expireAt = result.ExpireAt
}
return centrifuge.SubscribeReply{
Options: centrifuge.SubscribeOptions{
ExpireAt: expireAt,
ChannelInfo: info,
EmitPresence: presence,
EmitJoinLeave: joinLeave,
PushJoinLeave: pushJoinLeave,
EnableRecovery: recovery,
EnablePositioning: positioning,
RecoveryMode: recoveryMode,
AutoCacheRecover: recovery && chOpts.AutoCacheRecover && recoveryMode == centrifuge.RecoveryModeCache,
AllowedDeltaTypes: chOpts.AllowedDeltaTypes,
AllowTagsFilter: chOpts.AllowTagsFilter,
Data: data,
Source: subsource.SubscribeProxy,
HistoryMetaTTL: time.Duration(chOpts.HistoryMetaTTL),
},
ClientSideRefresh: true,
}, extra, nil
}
}