Skip to content
Snippets Groups Projects
server.go 2.41 KiB
Newer Older
  • Learn to ignore specific revisions
  • package server
    
    import (
    	"io"
    	"net"
    	"runtime/debug"
    
    	config "code.fbi.h-da.de/danet/costaquanta/ctrl/internal"
    	"code.fbi.h-da.de/danet/costaquanta/ctrl/internal/application"
    	"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/recovery"
    	"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
    	"go.uber.org/zap"
    	"google.golang.org/grpc"
    	"google.golang.org/grpc/codes"
    	"google.golang.org/grpc/status"
    )
    
    // GRPCServer is an interface that defines the methods for a gRPC server.
    // It is used to start the server and register services.
    // It also implements the io.Closer interface to allow for graceful shutdown.
    type GRPCServer interface {
    
    	Start(func(server *grpc.Server, app *application.Application))
    
    	io.Closer
    }
    
    type gRPCServer struct {
    	logger     *zap.SugaredLogger
    	grpcServer *grpc.Server
    	config     config.Config
    	app        *application.Application
    }
    
    func NewGrpcServer(
    	config config.Config,
    	app *application.Application,
    	logger *zap.SugaredLogger,
    ) (GRPCServer, error) {
    
    	options, err := buildOptions(logger)
    
    	if err != nil {
    		return nil, err
    	}
    
    	server := grpc.NewServer(options...)
    
    	return &gRPCServer{
    		config:     config,
    		grpcServer: server,
    		app:        app,
    		logger:     logger,
    	}, err
    }
    
    func (g gRPCServer) Start(serviceRegister func(server *grpc.Server, app *application.Application)) {
    	grpcListener, err := net.Listen("tcp", ""+g.config.AddrBind)
    	if err != nil {
    		g.logger.Fatal("failed to start grpc server", zap.Any("err", err))
    	}
    
    	serviceRegister(g.grpcServer, g.app)
    
    	g.logger.Info("start grpc server success ", zap.Any("endpoint", grpcListener.Addr()))
    	if err := g.grpcServer.Serve(grpcListener); err != nil {
    		g.logger.Fatal("failed to grpc server serve", zap.Any("err", err))
    	}
    }
    
    func (g gRPCServer) Close() error {
    	g.grpcServer.GracefulStop()
    
    	return nil
    }
    
    func buildOptions(
    	logs *zap.SugaredLogger,
    ) ([]grpc.ServerOption, error) {
    	grpcPanicRecoveryHandler := func(p any) (err error) {
    		logs.Errorf("recovered from gRPC panic %+v; %v", p, debug.Stack())
    
    		return status.Errorf(codes.Internal, "%s", p)
    	}
    
    	return []grpc.ServerOption{
    		grpc.StatsHandler(otelgrpc.NewServerHandler()),
    		grpc.ChainUnaryInterceptor(
    			recovery.UnaryServerInterceptor(recovery.WithRecoveryHandler(grpcPanicRecoveryHandler)),
    		),
    		grpc.ChainStreamInterceptor(
    			recovery.StreamServerInterceptor(
    				recovery.WithRecoveryHandler(grpcPanicRecoveryHandler),
    			),
    		),
    	}, nil
    }