// Package server provides the HTTP server implementation package server import ( "context" "fmt" "net/http" "os" "os/signal" "syscall" "time" "github.com/konduktor/konduktor/internal/config" "github.com/konduktor/konduktor/internal/logging" "github.com/konduktor/konduktor/internal/middleware" "github.com/konduktor/konduktor/internal/routing" ) const Version = "0.1.0" // Server represents the Konduktor HTTP server type Server struct { config *config.Config httpServer *http.Server router *routing.Router logger *logging.Logger } // New creates a new server instance func New(cfg *config.Config) (*Server, error) { if err := cfg.Validate(); err != nil { return nil, fmt.Errorf("invalid configuration: %w", err) } logger, err := logging.NewFromConfig(cfg.Logging) if err != nil { return nil, fmt.Errorf("failed to create logger: %w", err) } router := routing.New(cfg, logger) srv := &Server{ config: cfg, router: router, logger: logger, } return srv, nil } // Run starts the server and blocks until shutdown func (s *Server) Run() error { // Build handler chain with middleware handler := s.buildHandler() // Create HTTP server addr := fmt.Sprintf("%s:%d", s.config.Server.Host, s.config.Server.Port) s.httpServer = &http.Server{ Addr: addr, Handler: handler, ReadTimeout: 30 * time.Second, WriteTimeout: 30 * time.Second, IdleTimeout: 120 * time.Second, } // Start server in goroutine errChan := make(chan error, 1) go func() { s.logger.Info("Server starting", "addr", addr, "version", Version) var err error if s.config.SSL.Enabled { err = s.httpServer.ListenAndServeTLS(s.config.SSL.CertFile, s.config.SSL.KeyFile) } else { err = s.httpServer.ListenAndServe() } if err != nil && err != http.ErrServerClosed { errChan <- err } }() // Wait for shutdown signal return s.waitForShutdown(errChan) } // buildHandler builds the HTTP handler chain func (s *Server) buildHandler() http.Handler { var handler http.Handler = s.router // Add middleware handler = middleware.AccessLog(handler, s.logger) handler = middleware.ServerHeader(handler, Version) handler = middleware.Recovery(handler, s.logger) return handler } // waitForShutdown waits for shutdown signal and gracefully stops the server func (s *Server) waitForShutdown(errChan <-chan error) error { // Listen for shutdown signals sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) select { case err := <-errChan: return err case sig := <-sigChan: s.logger.Info("Shutdown signal received", "signal", sig.String()) } // Graceful shutdown with timeout ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() s.logger.Info("Shutting down server...") if err := s.httpServer.Shutdown(ctx); err != nil { s.logger.Error("Error during shutdown", "error", err) return err } s.logger.Info("Server stopped gracefully") return nil } // Shutdown gracefully shuts down the server func (s *Server) Shutdown(ctx context.Context) error { return s.httpServer.Shutdown(ctx) }