154 lines
4.8 KiB
Go
154 lines
4.8 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/centrifuge"
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/rs/zerolog/log"
|
|
)
|
|
|
|
// SubRefreshHandlerConfig ...
|
|
type SubRefreshHandlerConfig struct {
|
|
Proxies map[string]SubRefreshProxy
|
|
}
|
|
|
|
// SubRefreshHandler ...
|
|
type SubRefreshHandler struct {
|
|
config SubRefreshHandlerConfig
|
|
summary map[string]prometheus.Observer
|
|
histogram map[string]prometheus.Observer
|
|
errors map[string]prometheus.Counter
|
|
inflight map[string]prometheus.Gauge
|
|
}
|
|
|
|
// NewSubRefreshHandler ...
|
|
func NewSubRefreshHandler(c SubRefreshHandlerConfig) *SubRefreshHandler {
|
|
h := &SubRefreshHandler{
|
|
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(), "sub_refresh", name)
|
|
histogram[name] = metrics.ProxyCallDurationHistogram.WithLabelValues(p.Protocol(), "sub_refresh", name)
|
|
errors[name] = metrics.ProxyCallErrorCount.WithLabelValues(p.Protocol(), "sub_refresh", name)
|
|
inflight[name] = metrics.ProxyCallInflightRequests.WithLabelValues(p.Protocol(), "sub_refresh", name)
|
|
}
|
|
h.summary = summary
|
|
h.histogram = histogram
|
|
h.errors = errors
|
|
h.inflight = inflight
|
|
return h
|
|
}
|
|
|
|
type SubRefreshExtra struct {
|
|
}
|
|
|
|
// SubRefreshHandlerFunc ...
|
|
type SubRefreshHandlerFunc func(Client, centrifuge.SubRefreshEvent, configtypes.ChannelOptions, PerCallData) (centrifuge.SubRefreshReply, SubRefreshExtra, error)
|
|
|
|
// Handle refresh.
|
|
func (h *SubRefreshHandler) Handle() SubRefreshHandlerFunc {
|
|
return func(client Client, e centrifuge.SubRefreshEvent, chOpts configtypes.ChannelOptions, pcd PerCallData) (centrifuge.SubRefreshReply, SubRefreshExtra, error) {
|
|
started := time.Now()
|
|
|
|
var p SubRefreshProxy
|
|
var summary prometheus.Observer
|
|
var histogram prometheus.Observer
|
|
var errors prometheus.Counter
|
|
|
|
proxyEnabled := chOpts.SubRefreshProxyEnabled
|
|
proxyName := chOpts.SubRefreshProxyName
|
|
if !proxyEnabled {
|
|
log.Info().Str("channel", e.Channel).Msg("sub refresh proxy not configured for a channel")
|
|
return centrifuge.SubRefreshReply{}, SubRefreshExtra{}, 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.SubRefreshRequest{
|
|
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)
|
|
}
|
|
refreshRep, err := p.ProxySubRefresh(client.Context(), req)
|
|
duration := time.Since(started).Seconds()
|
|
if err != nil {
|
|
select {
|
|
case <-client.Context().Done():
|
|
// Client connection already closed.
|
|
return centrifuge.SubRefreshReply{}, SubRefreshExtra{}, 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 sub refresh")
|
|
// In case of an error give connection one more minute to live and
|
|
// then try to check again. This way we gracefully handle temporary
|
|
// problems on application backend side.
|
|
// NOTE: this interval must be configurable maybe, but for now looks
|
|
// like a reasonable value.
|
|
return centrifuge.SubRefreshReply{
|
|
ExpireAt: time.Now().Unix() + 60,
|
|
}, SubRefreshExtra{}, nil
|
|
}
|
|
summary.Observe(duration)
|
|
histogram.Observe(duration)
|
|
|
|
result := refreshRep.Result
|
|
if result == nil {
|
|
// Subscription will be unsubscribed.
|
|
log.Error().Msg("no sub refresh result found")
|
|
return centrifuge.SubRefreshReply{
|
|
Expired: true,
|
|
}, SubRefreshExtra{}, nil
|
|
}
|
|
|
|
if result.Expired {
|
|
return centrifuge.SubRefreshReply{
|
|
Expired: true,
|
|
}, SubRefreshExtra{}, nil
|
|
}
|
|
|
|
var info []byte
|
|
if result.B64Info != "" {
|
|
decodedInfo, err := base64.StdEncoding.DecodeString(result.B64Info)
|
|
if err != nil {
|
|
log.Error().Err(err).Str("client", client.ID()).Msg("error decoding base64 info")
|
|
return centrifuge.SubRefreshReply{}, SubRefreshExtra{}, centrifuge.ErrorInternal
|
|
}
|
|
info = decodedInfo
|
|
} else {
|
|
info = result.Info
|
|
}
|
|
|
|
extra := SubRefreshExtra{}
|
|
|
|
return centrifuge.SubRefreshReply{
|
|
ExpireAt: result.ExpireAt,
|
|
Info: info,
|
|
}, extra, nil
|
|
}
|
|
}
|