♻️ Better grpc map encoder / decoder

This commit is contained in:
LittleSheep 2024-10-20 15:22:19 +08:00
parent 71b8607e32
commit 18ce501089
16 changed files with 957 additions and 80 deletions

View File

@ -2,7 +2,7 @@
<project version="4"> <project version="4">
<component name="ProjectModuleManager"> <component name="ProjectModuleManager">
<modules> <modules>
<module fileurl="file://$PROJECT_DIR$/.idea/Dealer.iml" filepath="$PROJECT_DIR$/.idea/Dealer.iml" /> <module fileurl="file://$PROJECT_DIR$/.idea/Nexus.iml" filepath="$PROJECT_DIR$/.idea/Nexus.iml" />
</modules> </modules>
</component> </component>
</project> </project>

22
pkg/directory/command.go Normal file
View File

@ -0,0 +1,22 @@
package directory
const (
CommandMethodGet = "get"
CommandMethodPut = "put"
CommandMethodPatch = "patch"
CommandMethodPost = "post"
CommandMethodDelete = "delete"
)
type Command struct {
// The unique identifier of the command, different method command can hold the same command id
ID string `json:"id"`
// The method of the command, such as get, post, others; inspired by RESTful design
Method string `json:"method"`
// The tags of the command will be used to invoke the pre-command middlewares and post-command middlewares
Tags []string `json:"tags"`
// The implementation of the command, the handler is the service that will be invoked
Handler []*ServiceInstance `json:"handler"`
robinIndex uint
}

View File

@ -0,0 +1,62 @@
package directory
import (
"github.com/samber/lo"
"sync"
)
// In commands, we use the map and the mutex because it is usually read and only sometimes write
var commandDirectory = make(map[string]*Command)
var commandDirectoryMutex sync.Mutex
func GetCommandKey(id, method string) string {
return id + ":" + method
}
func AddCommand(id, method string, tags []string, handler *ServiceInstance) {
commandDirectoryMutex.Lock()
defer commandDirectoryMutex.Unlock()
ky := GetCommandKey(id, method)
if _, ok := commandDirectory[id]; !ok {
commandDirectory[id] = &Command{
ID: id,
Method: method,
Tags: tags,
Handler: []*ServiceInstance{handler},
}
} else {
commandDirectory[ky].Handler = append(commandDirectory[ky].Handler, handler)
commandDirectory[ky].Tags = lo.Uniq(append(commandDirectory[ky].Tags, tags...))
}
commandDirectory[ky].Handler = lo.UniqBy(commandDirectory[ky].Handler, func(item *ServiceInstance) string {
return item.ID
})
}
func GetCommandHandler(id, method string) *ServiceInstance {
commandDirectoryMutex.Lock()
defer commandDirectoryMutex.Unlock()
ky := GetCommandKey(id, method)
if val, ok := commandDirectory[ky]; ok {
if len(val.Handler) == 0 {
return nil
}
idx := val.robinIndex % uint(len(val.Handler))
val.robinIndex = idx + 1
return val.Handler[idx]
}
return nil
}
func RemoveCommand(id, method string) {
commandDirectoryMutex.Lock()
defer commandDirectoryMutex.Unlock()
ky := GetCommandKey(id, method)
delete(commandDirectory, ky)
}

View File

@ -0,0 +1,89 @@
package directory
import (
"context"
"git.solsynth.dev/hypernet/nexus/pkg/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"io"
"time"
)
type CommandRpcServer struct {
proto.UnimplementedCommandControllerServer
}
func (c CommandRpcServer) AddCommand(ctx context.Context, info *proto.CommandInfo) (*proto.AddCommandResponse, error) {
clientId, err := GetClientId(ctx)
if err != nil {
return nil, err
}
service := GetServiceInstanceByType(clientId)
if service == nil {
return nil, status.Errorf(codes.NotFound, "service not found")
}
AddCommand(info.GetId(), info.GetMethod(), info.GetTags(), service)
return &proto.AddCommandResponse{
IsSuccess: true,
}, nil
}
func (c CommandRpcServer) RemoveCommand(ctx context.Context, request *proto.CommandLookupRequest) (*proto.RemoveCommandResponse, error) {
RemoveCommand(request.GetId(), request.GetMethod())
return &proto.RemoveCommandResponse{
IsSuccess: true,
}, nil
}
func (c CommandRpcServer) SendCommand(ctx context.Context, argument *proto.CommandArgument) (*proto.CommandReturn, error) {
id := argument.GetCommand()
method := argument.GetMethod()
handler := GetCommandHandler(id, method)
if handler == nil {
return nil, status.Errorf(codes.NotFound, "command not found")
}
conn, err := handler.GetGrpcConn()
if err != nil {
return nil, status.Errorf(codes.Unavailable, "service unavailable")
}
contx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
return proto.NewCommandControllerClient(conn).SendCommand(contx, argument)
}
func (c CommandRpcServer) SendStreamCommand(g grpc.BidiStreamingServer[proto.CommandArgument, proto.CommandReturn]) error {
for {
pck, err := g.Recv()
if err == io.EOF {
return nil
} else if err != nil {
return err
}
id := pck.GetCommand()
method := pck.GetMethod()
handler := GetCommandHandler(id, method)
if handler == nil {
return status.Errorf(codes.NotFound, "command not found")
}
conn, err := handler.GetGrpcConn()
contx, cancel := context.WithTimeout(context.Background(), time.Second*10)
result, _ := proto.NewCommandControllerClient(conn).SendCommand(contx, pck)
cancel()
_ = g.Send(&proto.CommandReturn{
Status: result.Status,
Payload: result.Payload,
})
}
}

20
pkg/directory/exts.go Normal file
View File

@ -0,0 +1,20 @@
package directory
import (
"context"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)
func GetClientId(ctx context.Context) (string, error) {
var clientId string
if md, ok := metadata.FromIncomingContext(ctx); !ok {
return clientId, status.Errorf(codes.InvalidArgument, "missing metadata")
} else if val, ok := md["client_id"]; !ok || len(val) == 0 {
return clientId, status.Errorf(codes.Unauthenticated, "missing client_id in metadata")
} else {
clientId = val[0]
}
return clientId, nil
}

View File

@ -4,6 +4,7 @@ import (
"sync" "sync"
) )
// In services, we use sync.Map because it will be both often read and write
var serviceDirectory sync.Map var serviceDirectory sync.Map
func GetServiceInstance(id string) *ServiceInstance { func GetServiceInstance(id string) *ServiceInstance {

View File

@ -3,17 +3,19 @@ package directory
import ( import (
"context" "context"
"fmt" "fmt"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"git.solsynth.dev/hypernet/nexus/pkg/proto" "git.solsynth.dev/hypernet/nexus/pkg/proto"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/samber/lo" "github.com/samber/lo"
) )
type DirectoryRpcServer struct { type ServiceRpcServer struct {
proto.UnimplementedServiceDirectoryServer proto.UnimplementedServiceDirectoryServer
} }
func convertServiceToInfo(in *ServiceInstance) *proto.ServiceInfo { func instantiationService(in *ServiceInstance) *proto.ServiceInfo {
if in == nil { if in == nil {
return nil return nil
} }
@ -26,23 +28,23 @@ func convertServiceToInfo(in *ServiceInstance) *proto.ServiceInfo {
} }
} }
func (v *DirectoryRpcServer) GetService(ctx context.Context, request *proto.GetServiceRequest) (*proto.GetServiceResponse, error) { func (v *ServiceRpcServer) GetService(ctx context.Context, request *proto.GetServiceRequest) (*proto.GetServiceResponse, error) {
if request.Id != nil { if request.Id != nil {
out := GetServiceInstance(request.GetId()) out := GetServiceInstance(request.GetId())
return &proto.GetServiceResponse{ return &proto.GetServiceResponse{
Data: convertServiceToInfo(out), Data: instantiationService(out),
}, nil }, nil
} }
if request.Type != nil { if request.Type != nil {
out := GetServiceInstanceByType(request.GetType()) out := GetServiceInstanceByType(request.GetType())
return &proto.GetServiceResponse{ return &proto.GetServiceResponse{
Data: convertServiceToInfo(out), Data: instantiationService(out),
}, nil }, nil
} }
return nil, fmt.Errorf("no filter condition is provided") return nil, fmt.Errorf("no filter condition is provided")
} }
func (v *DirectoryRpcServer) ListService(ctx context.Context, request *proto.ListServiceRequest) (*proto.ListServiceResponse, error) { func (v *ServiceRpcServer) ListService(ctx context.Context, request *proto.ListServiceRequest) (*proto.ListServiceResponse, error) {
var out []*ServiceInstance var out []*ServiceInstance
if request.Type != nil { if request.Type != nil {
out = ListServiceInstanceByType(request.GetType()) out = ListServiceInstanceByType(request.GetType())
@ -51,27 +53,36 @@ func (v *DirectoryRpcServer) ListService(ctx context.Context, request *proto.Lis
} }
return &proto.ListServiceResponse{ return &proto.ListServiceResponse{
Data: lo.Map(out, func(item *ServiceInstance, index int) *proto.ServiceInfo { Data: lo.Map(out, func(item *ServiceInstance, index int) *proto.ServiceInfo {
return convertServiceToInfo(item) return instantiationService(item)
}), }),
}, nil }, nil
} }
func (v *DirectoryRpcServer) AddService(ctx context.Context, info *proto.ServiceInfo) (*proto.AddServiceResponse, error) { func (v *ServiceRpcServer) AddService(ctx context.Context, info *proto.ServiceInfo) (*proto.AddServiceResponse, error) {
clientId, err := GetClientId(ctx)
if err != nil {
return nil, err
}
if info.GetId() != clientId {
return nil, status.Errorf(codes.InvalidArgument, "client_id mismatch in metadata")
}
in := &ServiceInstance{ in := &ServiceInstance{
ID: info.GetId(), ID: clientId,
Type: info.GetType(), Type: info.GetType(),
Label: info.GetLabel(), Label: info.GetLabel(),
GrpcAddr: info.GetGrpcAddr(), GrpcAddr: info.GetGrpcAddr(),
HttpAddr: info.HttpAddr, HttpAddr: info.HttpAddr,
} }
AddServiceInstance(in) AddServiceInstance(in)
log.Info().Str("id", info.GetId()).Str("label", info.GetLabel()).Msg("New service added.") log.Info().Str("id", clientId).Str("label", info.GetLabel()).Msg("New service added.")
return &proto.AddServiceResponse{ return &proto.AddServiceResponse{
IsSuccess: true, IsSuccess: true,
}, nil }, nil
} }
func (v *DirectoryRpcServer) RemoveService(ctx context.Context, request *proto.RemoveServiceRequest) (*proto.RemoveServiceResponse, error) { func (v *ServiceRpcServer) RemoveService(ctx context.Context, request *proto.RemoveServiceRequest) (*proto.RemoveServiceResponse, error) {
RemoveServiceInstance(request.GetId()) RemoveServiceInstance(request.GetId())
log.Info().Str("id", request.GetId()).Msg("A service removed.") log.Info().Str("id", request.GetId()).Msg("A service removed.")
return &proto.RemoveServiceResponse{ return &proto.RemoveServiceResponse{

View File

@ -6,6 +6,7 @@ import (
) )
func MapAPIs(app *fiber.App) { func MapAPIs(app *fiber.App) {
// Some built-in public-accessible APIs
wellKnown := app.Group("/.well-known").Name("Well Known") wellKnown := app.Group("/.well-known").Name("Well Known")
{ {
wellKnown.Get("/", func(c *fiber.Ctx) error { wellKnown.Get("/", func(c *fiber.Ctx) error {
@ -15,19 +16,13 @@ func MapAPIs(app *fiber.App) {
wellKnown.Get("/directory/services", listExistsService) wellKnown.Get("/directory/services", listExistsService)
} }
app.All("/cgi/:service/*", forwardServiceRequest) // Common websocket gateway
app.Use(func(c *fiber.Ctx) error {
api := app.Group("/api").Name("API")
{
api.Use(func(c *fiber.Ctx) error {
/*if err := exts.EnsureAuthenticated(c); err != nil { /*if err := exts.EnsureAuthenticated(c); err != nil {
return err return err
}*/ }*/
return c.Next() return c.Next()
}).Get("/ws", websocket.New(listenWebsocket)) }).Get("/ws", websocket.New(listenWebsocket))
api.All("/*", func(c *fiber.Ctx) error { app.All("/cgi/:service/*", forwardServiceRequest)
return fiber.ErrNotFound
})
}
} }

View File

@ -25,7 +25,8 @@ func NewServer() *GrpcServer {
srv: grpc.NewServer(), srv: grpc.NewServer(),
} }
proto.RegisterServiceDirectoryServer(server.srv, &directory.DirectoryRpcServer{}) proto.RegisterServiceDirectoryServer(server.srv, &directory.ServiceRpcServer{})
proto.RegisterCommandControllerServer(server.srv, &directory.CommandRpcServer{})
proto.RegisterStreamControllerServer(server.srv, server) proto.RegisterStreamControllerServer(server.srv, server)
health.RegisterHealthServer(server.srv, server) health.RegisterHealthServer(server.srv, server)

View File

@ -1,43 +0,0 @@
package services
import (
"context"
firebase "firebase.google.com/go"
"github.com/sideshow/apns2"
"github.com/sideshow/apns2/token"
"github.com/spf13/viper"
"google.golang.org/api/option"
)
// ExtFire is a Firebase App client
var ExtFire *firebase.App
func SetupFirebase() error {
opt := option.WithCredentialsFile(viper.GetString("firebase_credentials"))
app, err := firebase.NewApp(context.Background(), nil, opt)
if err != nil {
return err
} else {
ExtFire = app
}
return nil
}
// ExtAPNS is an Apple Push Notification Services client
var ExtAPNS *apns2.Client
func SetupAPNS() error {
authKey, err := token.AuthKeyFromFile(viper.GetString("apns_credentials"))
if err != nil {
return err
}
ExtAPNS = apns2.NewTokenClient(&token.Token{
AuthKey: authKey,
KeyID: viper.GetString("apns_credentials_key"),
TeamID: viper.GetString("apns_credentials_team"),
}).Production()
return nil
}

View File

@ -5,8 +5,6 @@ import (
"os/signal" "os/signal"
"syscall" "syscall"
"git.solsynth.dev/hypernet/nexus/pkg/internal/services"
server "git.solsynth.dev/hypernet/nexus/pkg/http" server "git.solsynth.dev/hypernet/nexus/pkg/http"
pkg "git.solsynth.dev/hypernet/nexus/pkg/internal" pkg "git.solsynth.dev/hypernet/nexus/pkg/internal"
"git.solsynth.dev/hypernet/nexus/pkg/internal/grpc" "git.solsynth.dev/hypernet/nexus/pkg/internal/grpc"
@ -34,14 +32,6 @@ func main() {
log.Panic().Err(err).Msg("An error occurred when loading settings.") log.Panic().Err(err).Msg("An error occurred when loading settings.")
} }
// Set up external services
if err := services.SetupFirebase(); err != nil {
log.Warn().Err(err).Msg("An error occurred when setup firebase, firebase notification push is unavailable...")
}
if err := services.SetupAPNS(); err != nil {
log.Warn().Err(err).Msg("An error occurred when setup APNs, apple notification push is unavailable...")
}
// Server // Server
go server.NewServer().Listen() go server.NewServer().Listen()

457
pkg/proto/command.pb.go Normal file
View File

@ -0,0 +1,457 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.35.1
// protoc v5.28.2
// source: command.proto
package proto
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type CommandInfo struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
Method string `protobuf:"bytes,2,opt,name=method,proto3" json:"method,omitempty"`
Tags []string `protobuf:"bytes,3,rep,name=tags,proto3" json:"tags,omitempty"`
}
func (x *CommandInfo) Reset() {
*x = CommandInfo{}
mi := &file_command_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *CommandInfo) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CommandInfo) ProtoMessage() {}
func (x *CommandInfo) ProtoReflect() protoreflect.Message {
mi := &file_command_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use CommandInfo.ProtoReflect.Descriptor instead.
func (*CommandInfo) Descriptor() ([]byte, []int) {
return file_command_proto_rawDescGZIP(), []int{0}
}
func (x *CommandInfo) GetId() string {
if x != nil {
return x.Id
}
return ""
}
func (x *CommandInfo) GetMethod() string {
if x != nil {
return x.Method
}
return ""
}
func (x *CommandInfo) GetTags() []string {
if x != nil {
return x.Tags
}
return nil
}
type CommandLookupRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
Method string `protobuf:"bytes,2,opt,name=method,proto3" json:"method,omitempty"`
}
func (x *CommandLookupRequest) Reset() {
*x = CommandLookupRequest{}
mi := &file_command_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *CommandLookupRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CommandLookupRequest) ProtoMessage() {}
func (x *CommandLookupRequest) ProtoReflect() protoreflect.Message {
mi := &file_command_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use CommandLookupRequest.ProtoReflect.Descriptor instead.
func (*CommandLookupRequest) Descriptor() ([]byte, []int) {
return file_command_proto_rawDescGZIP(), []int{1}
}
func (x *CommandLookupRequest) GetId() string {
if x != nil {
return x.Id
}
return ""
}
func (x *CommandLookupRequest) GetMethod() string {
if x != nil {
return x.Method
}
return ""
}
type AddCommandResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
IsSuccess bool `protobuf:"varint,1,opt,name=is_success,json=isSuccess,proto3" json:"is_success,omitempty"`
}
func (x *AddCommandResponse) Reset() {
*x = AddCommandResponse{}
mi := &file_command_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *AddCommandResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AddCommandResponse) ProtoMessage() {}
func (x *AddCommandResponse) ProtoReflect() protoreflect.Message {
mi := &file_command_proto_msgTypes[2]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AddCommandResponse.ProtoReflect.Descriptor instead.
func (*AddCommandResponse) Descriptor() ([]byte, []int) {
return file_command_proto_rawDescGZIP(), []int{2}
}
func (x *AddCommandResponse) GetIsSuccess() bool {
if x != nil {
return x.IsSuccess
}
return false
}
type RemoveCommandResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
IsSuccess bool `protobuf:"varint,1,opt,name=is_success,json=isSuccess,proto3" json:"is_success,omitempty"`
}
func (x *RemoveCommandResponse) Reset() {
*x = RemoveCommandResponse{}
mi := &file_command_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *RemoveCommandResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*RemoveCommandResponse) ProtoMessage() {}
func (x *RemoveCommandResponse) ProtoReflect() protoreflect.Message {
mi := &file_command_proto_msgTypes[3]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use RemoveCommandResponse.ProtoReflect.Descriptor instead.
func (*RemoveCommandResponse) Descriptor() ([]byte, []int) {
return file_command_proto_rawDescGZIP(), []int{3}
}
func (x *RemoveCommandResponse) GetIsSuccess() bool {
if x != nil {
return x.IsSuccess
}
return false
}
type CommandArgument struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Command string `protobuf:"bytes,1,opt,name=command,proto3" json:"command,omitempty"`
Method string `protobuf:"bytes,2,opt,name=method,proto3" json:"method,omitempty"`
Payload []byte `protobuf:"bytes,3,opt,name=payload,proto3,oneof" json:"payload,omitempty"`
}
func (x *CommandArgument) Reset() {
*x = CommandArgument{}
mi := &file_command_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *CommandArgument) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CommandArgument) ProtoMessage() {}
func (x *CommandArgument) ProtoReflect() protoreflect.Message {
mi := &file_command_proto_msgTypes[4]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use CommandArgument.ProtoReflect.Descriptor instead.
func (*CommandArgument) Descriptor() ([]byte, []int) {
return file_command_proto_rawDescGZIP(), []int{4}
}
func (x *CommandArgument) GetCommand() string {
if x != nil {
return x.Command
}
return ""
}
func (x *CommandArgument) GetMethod() string {
if x != nil {
return x.Method
}
return ""
}
func (x *CommandArgument) GetPayload() []byte {
if x != nil {
return x.Payload
}
return nil
}
type CommandReturn struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Status int32 `protobuf:"varint,1,opt,name=status,proto3" json:"status,omitempty"`
Payload []byte `protobuf:"bytes,2,opt,name=payload,proto3,oneof" json:"payload,omitempty"`
}
func (x *CommandReturn) Reset() {
*x = CommandReturn{}
mi := &file_command_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *CommandReturn) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CommandReturn) ProtoMessage() {}
func (x *CommandReturn) ProtoReflect() protoreflect.Message {
mi := &file_command_proto_msgTypes[5]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use CommandReturn.ProtoReflect.Descriptor instead.
func (*CommandReturn) Descriptor() ([]byte, []int) {
return file_command_proto_rawDescGZIP(), []int{5}
}
func (x *CommandReturn) GetStatus() int32 {
if x != nil {
return x.Status
}
return 0
}
func (x *CommandReturn) GetPayload() []byte {
if x != nil {
return x.Payload
}
return nil
}
var File_command_proto protoreflect.FileDescriptor
var file_command_proto_rawDesc = []byte{
0x0a, 0x0d, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
0x05, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x49, 0x0a, 0x0b, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e,
0x64, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18,
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x12, 0x0a,
0x04, 0x74, 0x61, 0x67, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x74, 0x61, 0x67,
0x73, 0x22, 0x3e, 0x0a, 0x14, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x4c, 0x6f, 0x6f, 0x6b,
0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74,
0x68, 0x6f, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f,
0x64, 0x22, 0x33, 0x0a, 0x12, 0x41, 0x64, 0x64, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x73, 0x5f, 0x73, 0x75,
0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x73, 0x53,
0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x22, 0x36, 0x0a, 0x15, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65,
0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x1d, 0x0a, 0x0a, 0x69, 0x73, 0x5f, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20,
0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x73, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x22, 0x6e,
0x0a, 0x0f, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x41, 0x72, 0x67, 0x75, 0x6d, 0x65, 0x6e,
0x74, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x6d,
0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, 0x74,
0x68, 0x6f, 0x64, 0x12, 0x1d, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x03,
0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x88,
0x01, 0x01, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0x52,
0x0a, 0x0d, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x12,
0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52,
0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1d, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f,
0x61, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c,
0x6f, 0x61, 0x64, 0x88, 0x01, 0x01, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f,
0x61, 0x64, 0x32, 0xa8, 0x02, 0x0a, 0x11, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x43, 0x6f,
0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x12, 0x3d, 0x0a, 0x0a, 0x41, 0x64, 0x64, 0x43,
0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43,
0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x1a, 0x19, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x2e, 0x41, 0x64, 0x64, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4c, 0x0a, 0x0d, 0x52, 0x65, 0x6d, 0x6f, 0x76,
0x65, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65,
0x6d, 0x6f, 0x76, 0x65, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3d, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x6d,
0x6d, 0x61, 0x6e, 0x64, 0x12, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x6f, 0x6d,
0x6d, 0x61, 0x6e, 0x64, 0x41, 0x72, 0x67, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x1a, 0x14, 0x2e, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x74, 0x75,
0x72, 0x6e, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x11, 0x53, 0x65, 0x6e, 0x64, 0x53, 0x74, 0x72, 0x65,
0x61, 0x6d, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x41, 0x72, 0x67, 0x75, 0x6d, 0x65, 0x6e,
0x74, 0x1a, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e,
0x64, 0x52, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x42, 0x09, 0x5a,
0x07, 0x2e, 0x3b, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_command_proto_rawDescOnce sync.Once
file_command_proto_rawDescData = file_command_proto_rawDesc
)
func file_command_proto_rawDescGZIP() []byte {
file_command_proto_rawDescOnce.Do(func() {
file_command_proto_rawDescData = protoimpl.X.CompressGZIP(file_command_proto_rawDescData)
})
return file_command_proto_rawDescData
}
var file_command_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
var file_command_proto_goTypes = []any{
(*CommandInfo)(nil), // 0: proto.CommandInfo
(*CommandLookupRequest)(nil), // 1: proto.CommandLookupRequest
(*AddCommandResponse)(nil), // 2: proto.AddCommandResponse
(*RemoveCommandResponse)(nil), // 3: proto.RemoveCommandResponse
(*CommandArgument)(nil), // 4: proto.CommandArgument
(*CommandReturn)(nil), // 5: proto.CommandReturn
}
var file_command_proto_depIdxs = []int32{
0, // 0: proto.CommandController.AddCommand:input_type -> proto.CommandInfo
1, // 1: proto.CommandController.RemoveCommand:input_type -> proto.CommandLookupRequest
4, // 2: proto.CommandController.SendCommand:input_type -> proto.CommandArgument
4, // 3: proto.CommandController.SendStreamCommand:input_type -> proto.CommandArgument
2, // 4: proto.CommandController.AddCommand:output_type -> proto.AddCommandResponse
3, // 5: proto.CommandController.RemoveCommand:output_type -> proto.RemoveCommandResponse
5, // 6: proto.CommandController.SendCommand:output_type -> proto.CommandReturn
5, // 7: proto.CommandController.SendStreamCommand:output_type -> proto.CommandReturn
4, // [4:8] is the sub-list for method output_type
0, // [0:4] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_command_proto_init() }
func file_command_proto_init() {
if File_command_proto != nil {
return
}
file_command_proto_msgTypes[4].OneofWrappers = []any{}
file_command_proto_msgTypes[5].OneofWrappers = []any{}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_command_proto_rawDesc,
NumEnums: 0,
NumMessages: 6,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_command_proto_goTypes,
DependencyIndexes: file_command_proto_depIdxs,
MessageInfos: file_command_proto_msgTypes,
}.Build()
File_command_proto = out.File
file_command_proto_rawDesc = nil
file_command_proto_goTypes = nil
file_command_proto_depIdxs = nil
}

42
pkg/proto/command.proto Normal file
View File

@ -0,0 +1,42 @@
syntax = "proto3";
option go_package = ".;proto";
package proto;
service CommandController {
rpc AddCommand(CommandInfo) returns (AddCommandResponse) {}
rpc RemoveCommand(CommandLookupRequest) returns (RemoveCommandResponse) {}
rpc SendCommand(CommandArgument) returns (CommandReturn) {}
rpc SendStreamCommand(stream CommandArgument) returns (stream CommandReturn) {}
}
message CommandInfo {
string id = 1;
string method = 2;
repeated string tags = 3;
}
message CommandLookupRequest {
string id = 1;
string method = 2;
}
message AddCommandResponse {
bool is_success = 1;
}
message RemoveCommandResponse {
bool is_success = 1;
}
message CommandArgument {
string command = 1;
string method = 2;
optional bytes payload = 3;
}
message CommandReturn {
int32 status = 1;
optional bytes payload = 2;
}

View File

@ -0,0 +1,230 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.5.1
// - protoc v5.28.2
// source: command.proto
package proto
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 (
CommandController_AddCommand_FullMethodName = "/proto.CommandController/AddCommand"
CommandController_RemoveCommand_FullMethodName = "/proto.CommandController/RemoveCommand"
CommandController_SendCommand_FullMethodName = "/proto.CommandController/SendCommand"
CommandController_SendStreamCommand_FullMethodName = "/proto.CommandController/SendStreamCommand"
)
// CommandControllerClient is the client API for CommandController 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 CommandControllerClient interface {
AddCommand(ctx context.Context, in *CommandInfo, opts ...grpc.CallOption) (*AddCommandResponse, error)
RemoveCommand(ctx context.Context, in *CommandLookupRequest, opts ...grpc.CallOption) (*RemoveCommandResponse, error)
SendCommand(ctx context.Context, in *CommandArgument, opts ...grpc.CallOption) (*CommandReturn, error)
SendStreamCommand(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[CommandArgument, CommandReturn], error)
}
type commandControllerClient struct {
cc grpc.ClientConnInterface
}
func NewCommandControllerClient(cc grpc.ClientConnInterface) CommandControllerClient {
return &commandControllerClient{cc}
}
func (c *commandControllerClient) AddCommand(ctx context.Context, in *CommandInfo, opts ...grpc.CallOption) (*AddCommandResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(AddCommandResponse)
err := c.cc.Invoke(ctx, CommandController_AddCommand_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *commandControllerClient) RemoveCommand(ctx context.Context, in *CommandLookupRequest, opts ...grpc.CallOption) (*RemoveCommandResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(RemoveCommandResponse)
err := c.cc.Invoke(ctx, CommandController_RemoveCommand_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *commandControllerClient) SendCommand(ctx context.Context, in *CommandArgument, opts ...grpc.CallOption) (*CommandReturn, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(CommandReturn)
err := c.cc.Invoke(ctx, CommandController_SendCommand_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *commandControllerClient) SendStreamCommand(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[CommandArgument, CommandReturn], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &CommandController_ServiceDesc.Streams[0], CommandController_SendStreamCommand_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[CommandArgument, CommandReturn]{ClientStream: stream}
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 CommandController_SendStreamCommandClient = grpc.BidiStreamingClient[CommandArgument, CommandReturn]
// CommandControllerServer is the server API for CommandController service.
// All implementations must embed UnimplementedCommandControllerServer
// for forward compatibility.
type CommandControllerServer interface {
AddCommand(context.Context, *CommandInfo) (*AddCommandResponse, error)
RemoveCommand(context.Context, *CommandLookupRequest) (*RemoveCommandResponse, error)
SendCommand(context.Context, *CommandArgument) (*CommandReturn, error)
SendStreamCommand(grpc.BidiStreamingServer[CommandArgument, CommandReturn]) error
mustEmbedUnimplementedCommandControllerServer()
}
// UnimplementedCommandControllerServer 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 UnimplementedCommandControllerServer struct{}
func (UnimplementedCommandControllerServer) AddCommand(context.Context, *CommandInfo) (*AddCommandResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AddCommand not implemented")
}
func (UnimplementedCommandControllerServer) RemoveCommand(context.Context, *CommandLookupRequest) (*RemoveCommandResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RemoveCommand not implemented")
}
func (UnimplementedCommandControllerServer) SendCommand(context.Context, *CommandArgument) (*CommandReturn, error) {
return nil, status.Errorf(codes.Unimplemented, "method SendCommand not implemented")
}
func (UnimplementedCommandControllerServer) SendStreamCommand(grpc.BidiStreamingServer[CommandArgument, CommandReturn]) error {
return status.Errorf(codes.Unimplemented, "method SendStreamCommand not implemented")
}
func (UnimplementedCommandControllerServer) mustEmbedUnimplementedCommandControllerServer() {}
func (UnimplementedCommandControllerServer) testEmbeddedByValue() {}
// UnsafeCommandControllerServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to CommandControllerServer will
// result in compilation errors.
type UnsafeCommandControllerServer interface {
mustEmbedUnimplementedCommandControllerServer()
}
func RegisterCommandControllerServer(s grpc.ServiceRegistrar, srv CommandControllerServer) {
// If the following call pancis, it indicates UnimplementedCommandControllerServer 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(&CommandController_ServiceDesc, srv)
}
func _CommandController_AddCommand_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CommandInfo)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(CommandControllerServer).AddCommand(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: CommandController_AddCommand_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(CommandControllerServer).AddCommand(ctx, req.(*CommandInfo))
}
return interceptor(ctx, in, info, handler)
}
func _CommandController_RemoveCommand_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CommandLookupRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(CommandControllerServer).RemoveCommand(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: CommandController_RemoveCommand_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(CommandControllerServer).RemoveCommand(ctx, req.(*CommandLookupRequest))
}
return interceptor(ctx, in, info, handler)
}
func _CommandController_SendCommand_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CommandArgument)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(CommandControllerServer).SendCommand(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: CommandController_SendCommand_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(CommandControllerServer).SendCommand(ctx, req.(*CommandArgument))
}
return interceptor(ctx, in, info, handler)
}
func _CommandController_SendStreamCommand_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(CommandControllerServer).SendStreamCommand(&grpc.GenericServerStream[CommandArgument, CommandReturn]{ServerStream: stream})
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type CommandController_SendStreamCommandServer = grpc.BidiStreamingServer[CommandArgument, CommandReturn]
// CommandController_ServiceDesc is the grpc.ServiceDesc for CommandController service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var CommandController_ServiceDesc = grpc.ServiceDesc{
ServiceName: "proto.CommandController",
HandlerType: (*CommandControllerServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "AddCommand",
Handler: _CommandController_AddCommand_Handler,
},
{
MethodName: "RemoveCommand",
Handler: _CommandController_RemoveCommand_Handler,
},
{
MethodName: "SendCommand",
Handler: _CommandController_SendCommand_Handler,
},
},
Streams: []grpc.StreamDesc{
{
StreamName: "SendStreamCommand",
Handler: _CommandController_SendStreamCommand_Handler,
ServerStreams: true,
ClientStreams: true,
},
},
Metadata: "command.proto",
}