235 lines
5.9 KiB
Go
235 lines
5.9 KiB
Go
package transport
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net"
|
|
"sync"
|
|
"time"
|
|
|
|
pb "github.com/jeremytregunna/kevo/proto/kevo"
|
|
"github.com/jeremytregunna/kevo/pkg/grpc/service"
|
|
"github.com/jeremytregunna/kevo/pkg/transport"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/credentials"
|
|
"google.golang.org/grpc/keepalive"
|
|
)
|
|
|
|
// GRPCServer implements the transport.Server interface for gRPC
|
|
type GRPCServer struct {
|
|
address string
|
|
options transport.TransportOptions
|
|
server *grpc.Server
|
|
listener net.Listener
|
|
handler transport.RequestHandler
|
|
metrics transport.MetricsCollector
|
|
mu sync.Mutex
|
|
started bool
|
|
kevoImpl *service.KevoServiceServer
|
|
}
|
|
|
|
// NewGRPCServer creates a new gRPC server
|
|
func NewGRPCServer(address string, options transport.TransportOptions) (transport.Server, error) {
|
|
return &GRPCServer{
|
|
address: address,
|
|
options: options,
|
|
metrics: transport.NewMetricsCollector(),
|
|
}, nil
|
|
}
|
|
|
|
// Start starts the server and returns immediately
|
|
func (s *GRPCServer) Start() error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
if s.started {
|
|
return fmt.Errorf("server already started")
|
|
}
|
|
|
|
var serverOpts []grpc.ServerOption
|
|
|
|
// Configure TLS if enabled
|
|
if s.options.TLSEnabled {
|
|
tlsConfig := &tls.Config{
|
|
MinVersion: tls.VersionTLS12,
|
|
}
|
|
|
|
// Load server certificate if provided
|
|
if s.options.CertFile != "" && s.options.KeyFile != "" {
|
|
cert, err := tls.LoadX509KeyPair(s.options.CertFile, s.options.KeyFile)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load server certificate: %w", err)
|
|
}
|
|
tlsConfig.Certificates = []tls.Certificate{cert}
|
|
}
|
|
|
|
// Add credentials to server options
|
|
serverOpts = append(serverOpts, grpc.Creds(credentials.NewTLS(tlsConfig)))
|
|
}
|
|
|
|
// Configure keepalive parameters
|
|
keepaliveParams := keepalive.ServerParameters{
|
|
MaxConnectionIdle: 60 * time.Second,
|
|
MaxConnectionAge: 5 * time.Minute,
|
|
MaxConnectionAgeGrace: 5 * time.Second,
|
|
Time: 15 * time.Second,
|
|
Timeout: 5 * time.Second,
|
|
}
|
|
|
|
keepalivePolicy := keepalive.EnforcementPolicy{
|
|
MinTime: 5 * time.Second,
|
|
PermitWithoutStream: true,
|
|
}
|
|
|
|
serverOpts = append(serverOpts,
|
|
grpc.KeepaliveParams(keepaliveParams),
|
|
grpc.KeepaliveEnforcementPolicy(keepalivePolicy),
|
|
)
|
|
|
|
// Create gRPC server
|
|
s.server = grpc.NewServer(serverOpts...)
|
|
|
|
// Create listener
|
|
listener, err := net.Listen("tcp", s.address)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to listen on %s: %w", s.address, err)
|
|
}
|
|
s.listener = listener
|
|
|
|
// Set up service implementation
|
|
// Note: This is currently a placeholder. The actual implementation
|
|
// would require initializing the engine and transaction registry
|
|
// with real components. For now, we'll just register the "empty" service.
|
|
pb.RegisterKevoServiceServer(s.server, &placeholderKevoService{})
|
|
|
|
// Start serving in a goroutine
|
|
go func() {
|
|
if err := s.server.Serve(listener); err != nil {
|
|
fmt.Printf("gRPC server error: %v\n", err)
|
|
}
|
|
}()
|
|
|
|
s.started = true
|
|
return nil
|
|
}
|
|
|
|
// Serve starts the server and blocks until it's stopped
|
|
func (s *GRPCServer) Serve() error {
|
|
s.mu.Lock()
|
|
|
|
if s.started {
|
|
s.mu.Unlock()
|
|
return fmt.Errorf("server already started")
|
|
}
|
|
|
|
var serverOpts []grpc.ServerOption
|
|
|
|
// Configure TLS if enabled
|
|
if s.options.TLSEnabled {
|
|
tlsConfig := &tls.Config{
|
|
MinVersion: tls.VersionTLS12,
|
|
}
|
|
|
|
// Load server certificate if provided
|
|
if s.options.CertFile != "" && s.options.KeyFile != "" {
|
|
cert, err := tls.LoadX509KeyPair(s.options.CertFile, s.options.KeyFile)
|
|
if err != nil {
|
|
s.mu.Unlock()
|
|
return fmt.Errorf("failed to load server certificate: %w", err)
|
|
}
|
|
tlsConfig.Certificates = []tls.Certificate{cert}
|
|
}
|
|
|
|
// Add credentials to server options
|
|
serverOpts = append(serverOpts, grpc.Creds(credentials.NewTLS(tlsConfig)))
|
|
}
|
|
|
|
// Configure keepalive parameters
|
|
keepaliveParams := keepalive.ServerParameters{
|
|
MaxConnectionIdle: 60 * time.Second,
|
|
MaxConnectionAge: 5 * time.Minute,
|
|
MaxConnectionAgeGrace: 5 * time.Second,
|
|
Time: 15 * time.Second,
|
|
Timeout: 5 * time.Second,
|
|
}
|
|
|
|
keepalivePolicy := keepalive.EnforcementPolicy{
|
|
MinTime: 5 * time.Second,
|
|
PermitWithoutStream: true,
|
|
}
|
|
|
|
serverOpts = append(serverOpts,
|
|
grpc.KeepaliveParams(keepaliveParams),
|
|
grpc.KeepaliveEnforcementPolicy(keepalivePolicy),
|
|
)
|
|
|
|
// Create gRPC server
|
|
s.server = grpc.NewServer(serverOpts...)
|
|
|
|
// Create listener
|
|
listener, err := net.Listen("tcp", s.address)
|
|
if err != nil {
|
|
s.mu.Unlock()
|
|
return fmt.Errorf("failed to listen on %s: %w", s.address, err)
|
|
}
|
|
s.listener = listener
|
|
|
|
// Set up service implementation
|
|
// Note: This is currently a placeholder. The actual implementation
|
|
// would require initializing the engine and transaction registry
|
|
// with real components. For now, we'll just register the "empty" service.
|
|
pb.RegisterKevoServiceServer(s.server, &placeholderKevoService{})
|
|
|
|
s.started = true
|
|
s.mu.Unlock()
|
|
|
|
// This will block until the server is stopped
|
|
return s.server.Serve(listener)
|
|
}
|
|
|
|
// Stop stops the server gracefully
|
|
func (s *GRPCServer) Stop(ctx context.Context) error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
if !s.started {
|
|
return nil
|
|
}
|
|
|
|
stopped := make(chan struct{})
|
|
go func() {
|
|
s.server.GracefulStop()
|
|
close(stopped)
|
|
}()
|
|
|
|
select {
|
|
case <-stopped:
|
|
// Server stopped gracefully
|
|
case <-ctx.Done():
|
|
// Context deadline exceeded, force stop
|
|
s.server.Stop()
|
|
}
|
|
|
|
s.started = false
|
|
return nil
|
|
}
|
|
|
|
// SetRequestHandler sets the handler for incoming requests
|
|
func (s *GRPCServer) SetRequestHandler(handler transport.RequestHandler) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
s.handler = handler
|
|
}
|
|
|
|
// placeholderKevoService is a minimal implementation of KevoServiceServer for testing
|
|
type placeholderKevoService struct {
|
|
pb.UnimplementedKevoServiceServer
|
|
}
|
|
|
|
// Register server factory with transport registry
|
|
func init() {
|
|
transport.RegisterServerTransport("grpc", NewGRPCServer)
|
|
} |