205 lines
4.6 KiB
Go
205 lines
4.6 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log/slog"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/credentials/insecure"
|
|
"google.golang.org/grpc/reflection"
|
|
|
|
"go-grpc-gateway-template/internal/config"
|
|
"go-grpc-gateway-template/internal/health"
|
|
helloworldpb "go-grpc-gateway-template/proto/helloworld"
|
|
)
|
|
|
|
// Server wraps gRPC and HTTP servers with configuration
|
|
type Server struct {
|
|
config *config.Config
|
|
logger *slog.Logger
|
|
grpcServer *grpc.Server
|
|
httpServer *http.Server
|
|
healthHandler *health.Handler
|
|
}
|
|
|
|
// New creates a new server instance
|
|
func New(cfg *config.Config) *Server {
|
|
// Setup logger
|
|
var handler slog.Handler
|
|
if cfg.Log.Format == "json" {
|
|
handler = slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
|
|
Level: parseLogLevel(cfg.Log.Level),
|
|
})
|
|
} else {
|
|
handler = slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
|
|
Level: parseLogLevel(cfg.Log.Level),
|
|
})
|
|
}
|
|
logger := slog.New(handler)
|
|
|
|
// Create health handler
|
|
healthHandler := health.NewHandler("1.0.0")
|
|
|
|
return &Server{
|
|
config: cfg,
|
|
logger: logger,
|
|
healthHandler: healthHandler,
|
|
}
|
|
}
|
|
|
|
// Start starts both gRPC and HTTP servers
|
|
func (s *Server) Start(ctx context.Context) error {
|
|
var wg sync.WaitGroup
|
|
errChan := make(chan error, 2)
|
|
|
|
// Start gRPC server
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
if err := s.startGRPCServer(ctx); err != nil {
|
|
errChan <- fmt.Errorf("gRPC server error: %w", err)
|
|
}
|
|
}()
|
|
|
|
// Start HTTP server
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
if err := s.startHTTPServer(ctx); err != nil && err != http.ErrServerClosed {
|
|
errChan <- fmt.Errorf("HTTP server error: %w", err)
|
|
}
|
|
}()
|
|
|
|
// Wait for servers to start
|
|
go func() {
|
|
wg.Wait()
|
|
close(errChan)
|
|
}()
|
|
|
|
// Return first error if any
|
|
return <-errChan
|
|
}
|
|
|
|
func (s *Server) startGRPCServer(ctx context.Context) error {
|
|
lis, err := net.Listen("tcp", ":"+s.config.Server.GRPCPort)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to listen on port %s: %w", s.config.Server.GRPCPort, err)
|
|
}
|
|
|
|
s.grpcServer = grpc.NewServer()
|
|
|
|
// Register services
|
|
greeter := &GreeterService{}
|
|
helloworldpb.RegisterGreeterServer(s.grpcServer, greeter)
|
|
|
|
// Enable reflection for development
|
|
reflection.Register(s.grpcServer)
|
|
|
|
s.logger.Info("Starting gRPC server", "port", s.config.Server.GRPCPort)
|
|
|
|
return s.grpcServer.Serve(lis)
|
|
}
|
|
|
|
func (s *Server) startHTTPServer(ctx context.Context) error {
|
|
// Create gRPC client connection
|
|
conn, err := grpc.DialContext(
|
|
ctx,
|
|
"localhost:"+s.config.Server.GRPCPort,
|
|
grpc.WithBlock(),
|
|
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to dial gRPC server: %w", err)
|
|
}
|
|
|
|
// Create gateway mux
|
|
gwMux := runtime.NewServeMux()
|
|
|
|
// Register gRPC gateway handlers
|
|
err = helloworldpb.RegisterGreeterHandler(ctx, gwMux, conn)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to register gateway: %w", err)
|
|
}
|
|
|
|
// Create HTTP mux with health endpoint
|
|
mux := http.NewServeMux()
|
|
mux.Handle("/", gwMux)
|
|
mux.Handle("/health", s.healthHandler)
|
|
|
|
s.httpServer = &http.Server{
|
|
Addr: ":" + s.config.Server.HTTPPort,
|
|
Handler: mux,
|
|
ReadTimeout: 30 * time.Second,
|
|
WriteTimeout: 30 * time.Second,
|
|
IdleTimeout: 120 * time.Second,
|
|
}
|
|
|
|
s.logger.Info("Starting HTTP server", "port", s.config.Server.HTTPPort)
|
|
|
|
return s.httpServer.ListenAndServe()
|
|
}
|
|
|
|
// Shutdown gracefully shuts down the servers
|
|
func (s *Server) Shutdown(ctx context.Context) error {
|
|
s.logger.Info("Shutting down servers...")
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
// Shutdown HTTP server
|
|
if s.httpServer != nil {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
if err := s.httpServer.Shutdown(ctx); err != nil {
|
|
s.logger.Error("HTTP server shutdown error", "error", err)
|
|
}
|
|
}()
|
|
}
|
|
|
|
// Shutdown gRPC server
|
|
if s.grpcServer != nil {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
s.grpcServer.GracefulStop()
|
|
}()
|
|
}
|
|
|
|
wg.Wait()
|
|
s.logger.Info("Servers stopped")
|
|
return nil
|
|
}
|
|
|
|
// GreeterService implements the helloworld.Greeter service
|
|
type GreeterService struct {
|
|
helloworldpb.UnimplementedGreeterServer
|
|
}
|
|
|
|
// SayHello implements the SayHello RPC method
|
|
func (g *GreeterService) SayHello(ctx context.Context, req *helloworldpb.HelloRequest) (*helloworldpb.HelloReply, error) {
|
|
return &helloworldpb.HelloReply{
|
|
Message: "Hello " + req.GetName(),
|
|
}, nil
|
|
}
|
|
|
|
func parseLogLevel(level string) slog.Level {
|
|
switch level {
|
|
case "debug":
|
|
return slog.LevelDebug
|
|
case "info":
|
|
return slog.LevelInfo
|
|
case "warn":
|
|
return slog.LevelWarn
|
|
case "error":
|
|
return slog.LevelError
|
|
default:
|
|
return slog.LevelInfo
|
|
}
|
|
} |