feat: first
This commit is contained in:
205
internal/server/server.go
Normal file
205
internal/server/server.go
Normal file
@@ -0,0 +1,205 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user