Files
upstream-centrifugo/internal/proxy/publish_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

176 lines
5.6 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 proxy
import (
"encoding/base64"
"time"
"github.com/centrifugal/centrifugo/v6/internal/config"
"github.com/centrifugal/centrifugo/v6/internal/configtypes"
"github.com/centrifugal/centrifugo/v6/internal/metrics"
"github.com/centrifugal/centrifugo/v6/internal/proxyproto"
"github.com/centrifugal/centrifuge"
"github.com/prometheus/client_golang/prometheus"
"github.com/rs/zerolog/log"
)
// PublishHandlerConfig ...
type PublishHandlerConfig struct {
Proxies map[string]PublishProxy
}
// PublishHandler ...
type PublishHandler struct {
config PublishHandlerConfig
summary map[string]prometheus.Observer
histogram map[string]prometheus.Observer
errors map[string]prometheus.Counter
inflight map[string]prometheus.Gauge
}
// NewPublishHandler ...
func NewPublishHandler(c PublishHandlerConfig) *PublishHandler {
h := &PublishHandler{
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(), "publish", name)
histogram[name] = metrics.ProxyCallDurationHistogram.WithLabelValues(p.Protocol(), "publish", name)
errors[name] = metrics.ProxyCallErrorCount.WithLabelValues(p.Protocol(), "publish", name)
inflight[name] = metrics.ProxyCallInflightRequests.WithLabelValues(p.Protocol(), "publish", name)
}
h.summary = summary
h.histogram = histogram
h.errors = errors
h.inflight = inflight
return h
}
// PublishHandlerFunc ...
type PublishHandlerFunc func(Client, centrifuge.PublishEvent, configtypes.ChannelOptions, PerCallData) (centrifuge.PublishReply, error)
// Handle Publish.
func (h *PublishHandler) Handle(node *centrifuge.Node) PublishHandlerFunc {
return func(client Client, e centrifuge.PublishEvent, chOpts configtypes.ChannelOptions, pcd PerCallData) (centrifuge.PublishReply, error) {
started := time.Now()
var p PublishProxy
var summary prometheus.Observer
var histogram prometheus.Observer
var errors prometheus.Counter
proxyEnabled := chOpts.PublishProxyEnabled
proxyName := chOpts.PublishProxyName
if !proxyEnabled {
log.Info().Str("channel", e.Channel).Msg("publish proxy not enabled for a channel")
return centrifuge.PublishReply{}, 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.PublishRequest{
Client: client.ID(),
Protocol: string(client.Transport().Protocol()),
Transport: client.Transport().Name(),
Encoding: getEncoding(p.UseBase64()),
User: client.UserID(),
Channel: e.Channel,
}
if p.IncludeMeta() && pcd.Meta != nil {
req.Meta = proxyproto.Raw(pcd.Meta)
}
if !p.UseBase64() {
req.Data = e.Data
} else {
req.B64Data = base64.StdEncoding.EncodeToString(e.Data)
}
publishRep, err := p.ProxyPublish(client.Context(), req)
duration := time.Since(started).Seconds()
if err != nil {
select {
case <-client.Context().Done():
// Client connection already closed.
return centrifuge.PublishReply{}, 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 publish")
return centrifuge.PublishReply{}, err
}
summary.Observe(duration)
histogram.Observe(duration)
if publishRep.Disconnect != nil {
return centrifuge.PublishReply{}, proxyproto.DisconnectFromProto(publishRep.Disconnect)
}
if publishRep.Error != nil {
return centrifuge.PublishReply{}, proxyproto.ErrorFromProto(publishRep.Error)
}
historySize := chOpts.HistorySize
historyTTL := chOpts.HistoryTTL
historyMetaTTL := chOpts.HistoryMetaTTL
data := e.Data
var tags map[string]string
var idempotencyKey string
var useDelta bool
var version uint64
var versionEpoch string
if publishRep.Result != nil {
if publishRep.Result.Data != nil {
data = publishRep.Result.Data
} else if publishRep.Result.B64Data != "" {
decodedData, err := base64.StdEncoding.DecodeString(publishRep.Result.B64Data)
if err != nil {
log.Error().Err(err).Str("client", client.ID()).Msg("error decoding base64 data")
return centrifuge.PublishReply{}, centrifuge.ErrorInternal
}
data = decodedData
}
if publishRep.Result.SkipHistory {
historySize = 0
historyTTL = 0
}
tags = publishRep.Result.Tags
idempotencyKey = publishRep.Result.IdempotencyKey
useDelta = publishRep.Result.Delta
version = publishRep.Result.Version
versionEpoch = publishRep.Result.VersionEpoch
}
// The proxy may replace the data client sent, so validate the final data
// against the channel format – same policy as for the client-supplied one.
if err := config.ValidatePublicationData(data, chOpts.PublicationDataFormat); err != nil {
log.Info().Err(err).Str("channel", e.Channel).Str("client", client.ID()).Msg("publish proxy data validation failed")
return centrifuge.PublishReply{}, centrifuge.ErrorBadRequest
}
result, err := node.Publish(
e.Channel, data,
centrifuge.WithClientInfo(e.ClientInfo),
centrifuge.WithHistory(historySize, historyTTL.ToDuration(), historyMetaTTL.ToDuration()),
centrifuge.WithTags(tags),
centrifuge.WithIdempotencyKey(idempotencyKey),
centrifuge.WithDelta(useDelta),
centrifuge.WithVersion(version, versionEpoch),
)
return centrifuge.PublishReply{Result: &result}, err
}
}