How to run GRPC and REST server on same port in Go?

43 Views Asked by At

I am newbie in Go. I try to write server in Go. I write auth part in GRPC. But main functionality I want to build with REST. I write code for GRPC and REST. But when I launch my server, only REST works. My GRPC just not responding.

grpc.go application

type App struct {
    log        *slog.Logger
    gRPCServer *grpc.Server
    port       int
}

// New creates new gRPC server app.
func New(
    log *slog.Logger,
    authService authgrpc.Auth,
    port int,
) *App {

    recoveryOpts := []recovery.Option{
        recovery.WithRecoveryHandler(func(p interface{}) (err error) {
            log.Error("Recovered from panic", slog.Any("panic", p))

            return status.Errorf(codes.Internal, "internal error")
        }),
    }

    gRPCServer := grpc.NewServer(grpc.ChainUnaryInterceptor(
        recovery.UnaryServerInterceptor(recoveryOpts...),
        logging.UnaryServerInterceptor(InterceptorLogger(log), loggingOpts...),
    ))

    authgrpc.Register(gRPCServer, authService)

    return &App{
        log:        log,
        gRPCServer: gRPCServer,
        port:       port,
    }
}

// MustRun runs gRPC server and panics if any error occurs.
func (a *App) MustRun() {
    if err := a.Run(); err != nil {
        panic(err)
    }
}

// Run runs gRPC server.
func (a *App) Run() error {
    const op = "grpcapp.Run"

    l, err := net.Listen("tcp", fmt.Sprintf(":%d", a.port))
    if err != nil {
        return fmt.Errorf("%s: %w", op, err)
    }

    a.log.Info("grpc server started", slog.String("addr", l.Addr().String()))

    if err := a.gRPCServer.Serve(l); err != nil {
        return fmt.Errorf("%s: %w", op, err)
    }

    return nil
}

// Stop stops gRPC server.
func (a *App) Stop() {
    const op = "grpcapp.Stop"

    a.log.With(slog.String("op", op)).
        Info("stopping gRPC server", slog.Int("port", a.port))

    a.gRPCServer.GracefulStop()
}

rest.go application

type App struct {
    log         *slog.Logger
    httpServer  *http.Server
    port        int
    coreService *core.Core
}

func New(
    log *slog.Logger,
    coreService *core.Core,
    port int,
) *App {
    return &App{
        log:         log,
        port:        port,
        coreService: coreService,
    }
}

// MustRun runs the HTTP server and panics if any error occurs.
func (a *App) MustRun() {
    if err := a.Run(); err != nil {
        panic(err)
    }
}

// Run runs the HTTP server.
func (a *App) Run() error {
    const op = "restapp.Run"

    router := mux.NewRouter()
    router.HandleFunc("/author", a.coreService.GetAuthorHandler).Methods("GET")

    a.httpServer = &http.Server{
        Addr:    fmt.Sprintf(":%d", a.port),
        Handler: router,
    }

    if err := a.httpServer.ListenAndServe(); err != nil && errors.Is(err, http.ErrServerClosed) {
        return fmt.Errorf("%s: %w", op, err)
    }

    return nil
}

rest and grpc builders

type App struct {
    log        *slog.Logger
    GRPCServer *grpcapp.App
    RestServer *restapp.App
    port       int
}

func NewGrpc(
    log *slog.Logger,
    port int,
    storagePath string,
    tokenTTL time.Duration,
) *App {
    storage, err := sqlite.New(storagePath)
    if err != nil {
        panic(err)
    }

    authService := auth.New(log, storage, storage, storage, tokenTTL)
    grpcApp := grpcapp.New(log, authService, port)

    return &App{
        log:        log,
        GRPCServer: grpcApp,
        port:       port,
    }
}

func NewRest(
    log *slog.Logger,
    port int,
    storagePath string,
    tokenTTL time.Duration,
) *App {
    storage, err := sqlite.New(storagePath)
    if err != nil {
        panic(err)
    }

    coreService := core.New(log, storage, tokenTTL)
    restApp := restapp.New(log, coreService, port)

    return &App{
        log:        log,
        RestServer: restApp,
        port:       port,
    }
}

rest handler

type AuthorProvider interface {
    Author(ctx context.Context) ([]models.Author, error)
}

type Core struct {
    log            *slog.Logger
    authorProvider AuthorProvider
    tokenTTL       time.Duration
}

func New(
    log *slog.Logger,
    authorProvider AuthorProvider,
    tokenTTL time.Duration,
) *Core {
    return &Core{
        log:            log,
        authorProvider: authorProvider,
        tokenTTL:       tokenTTL,
    }
}

func (c *Core) GetAuthorHandler(w http.ResponseWriter, r *http.Request) {
    const op = "core.GetAuthorHandler"

    // Retrieve author data using AuthorProvider
    authors, err := c.authorProvider.Author(r.Context())
    if err != nil {
        http.Error(w, fmt.Sprintf("%s: %v", op, err), http.StatusInternalServerError)
        return
    }

    // Encode authors data as JSON response
    w.Header().Set("Content-Type", "application/json")
    if err := json.NewEncoder(w).Encode(authors); err != nil {
        http.Error(w, fmt.Sprintf("%s: %v", op, err), http.StatusInternalServerError)
        return
    }
}

I tried launch GRPC and REST on same and different ports. But it's not help. I also tried to put GRPC inside REST but it is little bit complex for me just now. Anyone can figure out how to fix it?

1

There are 1 best solutions below

0
Denis Popkov On

I run GRPC and REST on different ports and different goroutines:

func main() {
    cfg := config.MustLoad()

    log := setupLogger(cfg.Env)

    grpcApplication := app.NewGrpc(log, cfg.GRPC.Port, cfg.StoragePath, cfg.TokenTTL)
    restApplication := app.NewRest(log, cfg.GRPC.Port, cfg.StoragePath, cfg.TokenTTL)

    go func() {
        grpcApplication.GRPCServer.MustRun()
    }()

    go func() {
        restApplication.RestServer.MustRun()
    }()

    // Graceful shutdown

    stop := make(chan os.Signal, 1)
    signal.Notify(stop, syscall.SIGTERM, syscall.SIGINT)

    <-stop

    grpcApplication.GRPCServer.Stop()
    log.Info("Gracefully stopped")
}