snapshot of https://github.com/centrifugal/centrifugo.git (master, 2026-09-16; blobs > 131072B omitted)
This commit is contained in:
41
internal/unigrpc/codec.go
Normal file
41
internal/unigrpc/codec.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package unigrpc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
type rawFrame []byte
|
||||
|
||||
// RawCodec allows sending Protobuf encoded Pushes without
|
||||
// additional wrapping and marshaling.
|
||||
type RawCodec struct{}
|
||||
|
||||
func (c *RawCodec) Marshal(v any) ([]byte, error) {
|
||||
out, ok := v.(rawFrame)
|
||||
if !ok {
|
||||
vv, ok := v.(proto.Message)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to marshal, message is %T, want proto.Message", v)
|
||||
}
|
||||
return proto.Marshal(vv)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *RawCodec) Unmarshal(data []byte, v any) error {
|
||||
vv, ok := v.(proto.Message)
|
||||
if !ok {
|
||||
return fmt.Errorf("failed to unmarshal, message is %T, want proto.Message", v)
|
||||
}
|
||||
return proto.Unmarshal(data, vv)
|
||||
}
|
||||
|
||||
func (c *RawCodec) String() string {
|
||||
return "proto"
|
||||
}
|
||||
|
||||
func (c *RawCodec) Name() string {
|
||||
return "proto"
|
||||
}
|
||||
5
internal/unigrpc/config.go
Normal file
5
internal/unigrpc/config.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package unigrpc
|
||||
|
||||
import "github.com/centrifugal/centrifugo/v6/internal/configtypes"
|
||||
|
||||
type Config = configtypes.UniGRPC
|
||||
84
internal/unigrpc/grpc.go
Normal file
84
internal/unigrpc/grpc.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package unigrpc
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/centrifugal/centrifugo/v6/internal/logging"
|
||||
"github.com/centrifugal/centrifugo/v6/internal/unigrpc/unistream"
|
||||
|
||||
"github.com/centrifugal/centrifuge"
|
||||
"github.com/centrifugal/protocol"
|
||||
"github.com/rs/zerolog/log"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// RegisterService ...
|
||||
func RegisterService(server *grpc.Server, service unistream.CentrifugoUniStreamServer) error {
|
||||
unistream.RegisterCentrifugoUniStreamServer(server, service)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Service can work with client GRPC connections.
|
||||
type Service struct {
|
||||
unistream.UnimplementedCentrifugoUniStreamServer
|
||||
config Config
|
||||
node *centrifuge.Node
|
||||
}
|
||||
|
||||
// NewService creates new Service.
|
||||
func NewService(n *centrifuge.Node, c Config) *Service {
|
||||
return &Service{
|
||||
config: c,
|
||||
node: n,
|
||||
}
|
||||
}
|
||||
|
||||
// Consume is a unidirectional server->client stream with real-time data.
|
||||
func (s *Service) Consume(req *unistream.ConnectRequest, stream unistream.CentrifugoUniStream_ConsumeServer) error {
|
||||
streamDataCh := make(chan rawFrame)
|
||||
transport := newGRPCTransport(stream, streamDataCh)
|
||||
|
||||
connectRequest := &protocol.ConnectRequest{
|
||||
Token: req.Token,
|
||||
Data: req.Data,
|
||||
Name: req.Name,
|
||||
Version: req.Version,
|
||||
Headers: req.Headers,
|
||||
}
|
||||
if req.Subs != nil {
|
||||
subs := make(map[string]*protocol.SubscribeRequest, len(req.Subs))
|
||||
for k, v := range req.Subs {
|
||||
subs[k] = &protocol.SubscribeRequest{
|
||||
Recover: v.Recover,
|
||||
Offset: v.Offset,
|
||||
Epoch: v.Epoch,
|
||||
}
|
||||
}
|
||||
connectRequest.Subs = subs
|
||||
}
|
||||
|
||||
c, closeFn, err := centrifuge.NewClient(stream.Context(), s.node, transport)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = closeFn() }()
|
||||
|
||||
if logging.Enabled(logging.DebugLevel) {
|
||||
log.Debug().Str("transport", transport.Name()).Str("client", c.ID()).Msg("client connection established")
|
||||
defer func(started time.Time) {
|
||||
log.Debug().Str("transport", transport.Name()).Str("client", c.ID()).
|
||||
Str("duration", time.Since(started).String()).Msg("client connection completed")
|
||||
}(time.Now())
|
||||
}
|
||||
|
||||
c.ProtocolConnect(connectRequest)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-transport.closeCh:
|
||||
return nil
|
||||
case <-stream.Context().Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
97
internal/unigrpc/transport.go
Normal file
97
internal/unigrpc/transport.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package unigrpc
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/centrifugal/centrifugo/v6/internal/unigrpc/unistream"
|
||||
|
||||
"github.com/centrifugal/centrifuge"
|
||||
)
|
||||
|
||||
// grpcTransport wraps a stream.
|
||||
type grpcTransport struct {
|
||||
mu sync.RWMutex
|
||||
stream unistream.CentrifugoUniStream_ConsumeServer
|
||||
closed bool
|
||||
closeCh chan struct{}
|
||||
streamDataCh chan rawFrame
|
||||
}
|
||||
|
||||
func newGRPCTransport(stream unistream.CentrifugoUniStream_ConsumeServer, streamDataCh chan rawFrame) *grpcTransport {
|
||||
return &grpcTransport{
|
||||
stream: stream,
|
||||
streamDataCh: streamDataCh,
|
||||
closeCh: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
const transportName = "uni_grpc"
|
||||
|
||||
func (t *grpcTransport) Name() string {
|
||||
return transportName
|
||||
}
|
||||
|
||||
func (t *grpcTransport) AcceptProtocol() string {
|
||||
return "h2" // Always for GRPC for now.
|
||||
}
|
||||
|
||||
func (t *grpcTransport) Protocol() centrifuge.ProtocolType {
|
||||
return centrifuge.ProtocolTypeProtobuf
|
||||
}
|
||||
|
||||
// ProtocolVersion returns transport protocol version.
|
||||
func (t *grpcTransport) ProtocolVersion() centrifuge.ProtocolVersion {
|
||||
return centrifuge.ProtocolVersion2
|
||||
}
|
||||
|
||||
// Unidirectional returns whether transport is unidirectional.
|
||||
func (t *grpcTransport) Unidirectional() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// DisabledPushFlags ...
|
||||
func (t *grpcTransport) DisabledPushFlags() uint64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// PingPongConfig ...
|
||||
func (t *grpcTransport) PingPongConfig() centrifuge.PingPongConfig {
|
||||
return centrifuge.PingPongConfig{
|
||||
PingInterval: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// Emulation ...
|
||||
func (t *grpcTransport) Emulation() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *grpcTransport) Write(message []byte) error {
|
||||
return t.WriteMany(message)
|
||||
}
|
||||
|
||||
func (t *grpcTransport) WriteMany(messages ...[]byte) error {
|
||||
t.mu.RLock()
|
||||
if t.closed {
|
||||
t.mu.RUnlock()
|
||||
return nil
|
||||
}
|
||||
t.mu.RUnlock()
|
||||
for i := 0; i < len(messages); i++ {
|
||||
err := t.stream.SendMsg(rawFrame(messages[i]))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *grpcTransport) Close(_ centrifuge.Disconnect) error {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if t.closed {
|
||||
return nil
|
||||
}
|
||||
close(t.closeCh)
|
||||
return nil
|
||||
}
|
||||
11
internal/unigrpc/unistream/generate.sh
Normal file
11
internal/unigrpc/unistream/generate.sh
Normal file
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
|
||||
# go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
|
||||
|
||||
protoc -I ./ \
|
||||
unistream.proto \
|
||||
--go_out=. \
|
||||
--go-grpc_out=.
|
||||
1222
internal/unigrpc/unistream/unistream.pb.go
Normal file
1222
internal/unigrpc/unistream/unistream.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
127
internal/unigrpc/unistream/unistream.proto
Normal file
127
internal/unigrpc/unistream/unistream.proto
Normal file
@@ -0,0 +1,127 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package centrifugal.centrifugo.unistream;
|
||||
|
||||
option go_package = "./;unistream";
|
||||
|
||||
service CentrifugoUniStream {
|
||||
rpc Consume(ConnectRequest) returns (stream Push);
|
||||
}
|
||||
|
||||
message ConnectRequest {
|
||||
// Numeration is in sync with Centrifuge client protocol.
|
||||
string token = 1;
|
||||
bytes data = 2;
|
||||
map<string, SubscribeRequest> subs = 3;
|
||||
string name = 4;
|
||||
string version = 5;
|
||||
map<string, string> headers = 6;
|
||||
}
|
||||
|
||||
message SubscribeRequest {
|
||||
// Numeration is in sync with Centrifuge client protocol.
|
||||
bool recover = 3;
|
||||
reserved 4, 5;
|
||||
string epoch = 6;
|
||||
uint64 offset = 7;
|
||||
}
|
||||
|
||||
// Push can be sent to a client as part of Reply in case of bidirectional transport or
|
||||
// without additional wrapping in case of unidirectional transports.
|
||||
// ProtocolVersion2 uses channel and one of the possible concrete push messages.
|
||||
message Push {
|
||||
reserved 1, 3;
|
||||
string channel = 2;
|
||||
|
||||
// ProtocolVersion2 server can push one of the following fields to the client. We are
|
||||
// not using oneof here due to JSON interoperability concerns.
|
||||
Publication pub = 4;
|
||||
Join join = 5;
|
||||
Leave leave = 6;
|
||||
Unsubscribe unsubscribe = 7;
|
||||
Message message = 8;
|
||||
Subscribe subscribe = 9;
|
||||
Connect connect = 10;
|
||||
Disconnect disconnect = 11;
|
||||
Refresh refresh = 12;
|
||||
}
|
||||
|
||||
message ClientInfo {
|
||||
string user = 1;
|
||||
string client = 2;
|
||||
bytes conn_info = 3;
|
||||
bytes chan_info = 4;
|
||||
}
|
||||
|
||||
message Publication {
|
||||
reserved 1, 2, 3;
|
||||
bytes data = 4;
|
||||
ClientInfo info = 5;
|
||||
uint64 offset = 6;
|
||||
map<string, string> tags = 7;
|
||||
}
|
||||
|
||||
message Join {
|
||||
ClientInfo info = 1;
|
||||
}
|
||||
|
||||
message Leave {
|
||||
ClientInfo info = 1;
|
||||
}
|
||||
|
||||
message Unsubscribe {
|
||||
reserved 1;
|
||||
uint32 code = 2;
|
||||
string reason = 3;
|
||||
}
|
||||
|
||||
message Subscribe {
|
||||
bool recoverable = 1;
|
||||
reserved 2, 3;
|
||||
string epoch = 4;
|
||||
uint64 offset = 5;
|
||||
bool positioned = 6;
|
||||
bytes data = 7;
|
||||
}
|
||||
|
||||
message Message {
|
||||
bytes data = 1;
|
||||
}
|
||||
|
||||
message Connect {
|
||||
string client = 1;
|
||||
string version = 2;
|
||||
bytes data = 3;
|
||||
map<string, SubscribeResult> subs = 4;
|
||||
bool expires = 5;
|
||||
uint32 ttl = 6;
|
||||
uint32 ping = 7;
|
||||
bool pong = 8;
|
||||
string session = 9;
|
||||
string node = 10;
|
||||
}
|
||||
|
||||
message Disconnect {
|
||||
uint32 code = 1;
|
||||
string reason = 2;
|
||||
bool reconnect = 3;
|
||||
}
|
||||
|
||||
message Refresh {
|
||||
bool expires = 1;
|
||||
uint32 ttl = 2;
|
||||
}
|
||||
|
||||
message SubscribeResult {
|
||||
bool expires = 1;
|
||||
uint32 ttl = 2;
|
||||
bool recoverable = 3;
|
||||
reserved 4, 5;
|
||||
string epoch = 6;
|
||||
repeated Publication publications = 7;
|
||||
bool recovered = 8;
|
||||
uint64 offset = 9;
|
||||
bool positioned = 10;
|
||||
bytes data = 11;
|
||||
bool was_recovering = 12;
|
||||
}
|
||||
124
internal/unigrpc/unistream/unistream_grpc.pb.go
Normal file
124
internal/unigrpc/unistream/unistream_grpc.pb.go
Normal file
@@ -0,0 +1,124 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.6.0
|
||||
// - protoc v7.34.0
|
||||
// source: unistream.proto
|
||||
|
||||
package unistream
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.64.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion9
|
||||
|
||||
const (
|
||||
CentrifugoUniStream_Consume_FullMethodName = "/centrifugal.centrifugo.unistream.CentrifugoUniStream/Consume"
|
||||
)
|
||||
|
||||
// CentrifugoUniStreamClient is the client API for CentrifugoUniStream service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type CentrifugoUniStreamClient interface {
|
||||
Consume(ctx context.Context, in *ConnectRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Push], error)
|
||||
}
|
||||
|
||||
type centrifugoUniStreamClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewCentrifugoUniStreamClient(cc grpc.ClientConnInterface) CentrifugoUniStreamClient {
|
||||
return ¢rifugoUniStreamClient{cc}
|
||||
}
|
||||
|
||||
func (c *centrifugoUniStreamClient) Consume(ctx context.Context, in *ConnectRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Push], error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
stream, err := c.cc.NewStream(ctx, &CentrifugoUniStream_ServiceDesc.Streams[0], CentrifugoUniStream_Consume_FullMethodName, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &grpc.GenericClientStream[ConnectRequest, Push]{ClientStream: stream}
|
||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := x.ClientStream.CloseSend(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type CentrifugoUniStream_ConsumeClient = grpc.ServerStreamingClient[Push]
|
||||
|
||||
// CentrifugoUniStreamServer is the server API for CentrifugoUniStream service.
|
||||
// All implementations must embed UnimplementedCentrifugoUniStreamServer
|
||||
// for forward compatibility.
|
||||
type CentrifugoUniStreamServer interface {
|
||||
Consume(*ConnectRequest, grpc.ServerStreamingServer[Push]) error
|
||||
mustEmbedUnimplementedCentrifugoUniStreamServer()
|
||||
}
|
||||
|
||||
// UnimplementedCentrifugoUniStreamServer must be embedded to have
|
||||
// forward compatible implementations.
|
||||
//
|
||||
// NOTE: this should be embedded by value instead of pointer to avoid a nil
|
||||
// pointer dereference when methods are called.
|
||||
type UnimplementedCentrifugoUniStreamServer struct{}
|
||||
|
||||
func (UnimplementedCentrifugoUniStreamServer) Consume(*ConnectRequest, grpc.ServerStreamingServer[Push]) error {
|
||||
return status.Error(codes.Unimplemented, "method Consume not implemented")
|
||||
}
|
||||
func (UnimplementedCentrifugoUniStreamServer) mustEmbedUnimplementedCentrifugoUniStreamServer() {}
|
||||
func (UnimplementedCentrifugoUniStreamServer) testEmbeddedByValue() {}
|
||||
|
||||
// UnsafeCentrifugoUniStreamServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to CentrifugoUniStreamServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeCentrifugoUniStreamServer interface {
|
||||
mustEmbedUnimplementedCentrifugoUniStreamServer()
|
||||
}
|
||||
|
||||
func RegisterCentrifugoUniStreamServer(s grpc.ServiceRegistrar, srv CentrifugoUniStreamServer) {
|
||||
// If the following call panics, it indicates UnimplementedCentrifugoUniStreamServer was
|
||||
// embedded by pointer and is nil. This will cause panics if an
|
||||
// unimplemented method is ever invoked, so we test this at initialization
|
||||
// time to prevent it from happening at runtime later due to I/O.
|
||||
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
|
||||
t.testEmbeddedByValue()
|
||||
}
|
||||
s.RegisterService(&CentrifugoUniStream_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _CentrifugoUniStream_Consume_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
m := new(ConnectRequest)
|
||||
if err := stream.RecvMsg(m); err != nil {
|
||||
return err
|
||||
}
|
||||
return srv.(CentrifugoUniStreamServer).Consume(m, &grpc.GenericServerStream[ConnectRequest, Push]{ServerStream: stream})
|
||||
}
|
||||
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type CentrifugoUniStream_ConsumeServer = grpc.ServerStreamingServer[Push]
|
||||
|
||||
// CentrifugoUniStream_ServiceDesc is the grpc.ServiceDesc for CentrifugoUniStream service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var CentrifugoUniStream_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "centrifugal.centrifugo.unistream.CentrifugoUniStream",
|
||||
HandlerType: (*CentrifugoUniStreamServer)(nil),
|
||||
Methods: []grpc.MethodDesc{},
|
||||
Streams: []grpc.StreamDesc{
|
||||
{
|
||||
StreamName: "Consume",
|
||||
Handler: _CentrifugoUniStream_Consume_Handler,
|
||||
ServerStreams: true,
|
||||
},
|
||||
},
|
||||
Metadata: "unistream.proto",
|
||||
}
|
||||
Reference in New Issue
Block a user