// 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/extension" "github.com/konduktor/konduktor/internal/logging" "github.com/konduktor/konduktor/internal/middleware" ) const Version = "0.2.0" // Server represents the Konduktor HTTP server type Server struct { config *config.Config httpServer *http.Server extensionManager *extension.Manager 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) } // Create extension manager extManager := extension.NewManager(logger) // Load extensions from config for _, extCfg := range cfg.Extensions { // Add static_dir to routing config if not present if extCfg.Type == "routing" { if extCfg.Config == nil { extCfg.Config = make(map[string]interface{}) } if _, ok := extCfg.Config["static_dir"]; !ok { extCfg.Config["static_dir"] = cfg.HTTP.StaticDir } } if err := extManager.LoadExtension(extCfg.Type, extCfg.Config); err != nil { logger.Error("Failed to load extension", "type", extCfg.Type, "error", err) // Continue loading other extensions } } srv := &Server{ config: cfg, extensionManager: extManager, 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 { // Create base handler that returns 404 baseHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.NotFound(w, r) }) // Wrap with extension manager var handler http.Handler = s.extensionManager.Handler(baseHandler) // Add middleware (applied in reverse order) 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...") // Cleanup extensions s.extensionManager.Cleanup() 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) }