package bootstrap import ( "context" "errors" "fmt" "net/http" "os" "os/signal" "sync" "syscall" "time" "cloudnest/internal/config" "cloudnest/internal/pkg/logger" "github.com/gin-gonic/gin" ) // Server encapsulates HTTP server lifecycle management with graceful start/restart/shutdown. // Server 封装了 HTTP 服务器的生命周期管理,支持优雅启动/重启/关闭。 // // Features: // 1. Graceful startup with port auto-detection in dev mode // 2. Graceful shutdown on SIGINT/SIGTERM signals // 3. Hot reload on SIGHUP signal (Linux/Mac only) - re-reads config // 4. Waits for in-flight requests to complete before shutdown // // 特性: // 1. 开发模式下优雅启动,自动检测可用端口 // 2. 收到 SIGINT/SIGTERM 信号时优雅关闭 // 3. 收到 SIGHUP 信号时热重载(仅 Linux/Mac),重新读取配置 // 4. 关闭前等待正在进行的请求完成 type Server struct { cfg *config.Config engine *gin.Engine srv *http.Server mu sync.RWMutex reloadChan chan os.Signal shutdownOnce sync.Once } // NewServer creates a new Server instance with the given config and engine. // NewServer 使用给定的配置和引擎创建新的 Server 实例。 // // Parameters: // - cfg: The application configuration. // - engine: The Gin engine to serve. // // Returns: // - *Server: A new Server instance. // // 参数: // - cfg: 应用程序配置。 // - engine: 要服务的 Gin 引擎。 // // 返回值: // - *Server: 新的 Server 实例。 func NewServer(cfg *config.Config, engine *gin.Engine) *Server { return &Server{ cfg: cfg, engine: engine, reloadChan: make(chan os.Signal, 1), } } // Run starts the server and blocks until a shutdown signal is received. // Run 启动服务器并阻塞,直到收到关闭信号。 // // Lifecycle: // 1. Auto-detect available port (dev mode only) // 2. Start listening in a goroutine // 3. Register signal handlers (SIGINT, SIGTERM for shutdown; SIGHUP for reload on Unix) // 4. Block until shutdown signal // 5. Gracefully shut down with timeout // // 生命周期: // 1. 自动检测可用端口(仅开发模式) // 2. 在 goroutine 中开始监听 // 3. 注册信号处理器(SIGINT、SIGTERM 用于关闭;SIGHUP 用于 Unix 上的重载) // 4. 阻塞直到收到关闭信号 // 5. 使用超时优雅关闭 // // Returns: // - error: An error if startup or shutdown fails. // // 返回值: // - error: 如果启动或关闭失败则返回错误。 func (s *Server) Run() error { port := s.resolvePort() addr := fmt.Sprintf("%s:%d", s.cfg.Server.Host, port) s.mu.Lock() s.srv = &http.Server{ Addr: addr, Handler: s.engine, } s.mu.Unlock() // Register signal handlers / 注册信号处理器 sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) // Register SIGHUP for hot reload (Unix only) // 注册 SIGHUP 用于热重载(仅 Unix) if isUnix() { signal.Notify(s.reloadChan, syscall.SIGHUP) } // Start server in a goroutine / 在 goroutine 中启动服务器 serverErr := make(chan error, 1) go func() { logger.Info("server listening", "address", addr, "env", s.cfg.App.Env) if err := s.srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { serverErr <- err } close(serverErr) }() // Wait for signal / 等待信号 select { case sig := <-sigChan: logger.Info("shutdown signal received", "signal", sig.String()) return s.Shutdown() case sig := <-s.reloadChan: logger.Info("reload signal received, restarting", "signal", sig.String()) if err := s.Shutdown(); err != nil { return err } // After shutdown, the caller should restart the process // 关闭后,调用方应重启进程 return ErrReloadRequested case err := <-serverErr: if err != nil { logger.Error("server failed", "error", err) return err } return nil } } // Shutdown gracefully shuts down the server with a timeout. // Shutdown 使用超时优雅关闭服务器。 // // Parameters: // - timeout: The maximum time to wait for in-flight requests to complete. // // Returns: // - error: An error if shutdown fails. // // Note: // - Safe to call multiple times (subsequent calls are no-ops). // // 参数: // - timeout: 等待正在进行的请求完成的最长时间。 // // 返回值: // - error: 如果关闭失败则返回错误。 // // 注意: // - 可以安全地多次调用(后续调用是空操作)。 func (s *Server) Shutdown() error { var shutdownErr error s.shutdownOnce.Do(func() { s.mu.RLock() srv := s.srv s.mu.RUnlock() if srv == nil { return } timeout := time.Duration(s.cfg.Server.GracefulShutdownTimeout) * time.Second ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() logger.Info("graceful shutdown started", "timeout_seconds", timeout) if err := srv.Shutdown(ctx); err != nil { logger.Error("graceful shutdown failed, forcing close", "error", err) if closeErr := srv.Close(); closeErr != nil { shutdownErr = fmt.Errorf("forced close failed: %w (original: %v)", closeErr, err) return } shutdownErr = err } logger.Info("server exited gracefully") }) return shutdownErr } // resolvePort finds an available port starting from the configured port. // resolvePort 从配置的端口开始查找可用端口。 // // In dev mode: tries configured port, then port+1, port+2, ..., up to +9. // In prod mode: uses the configured port directly (no auto-switch). // // 开发模式:尝试配置端口,然后 port+1, port+2, ..., 最多 +9。 // 生产模式:直接使用配置的端口(不自动切换)。 // // Returns: // - int: The actual port to use. // // 返回值: // - int: 实际使用的端口。 func (s *Server) resolvePort() int { port := s.cfg.Server.Port if !s.cfg.IsDev() { return port } availablePort, err := config.FindAvailablePort(port, 10) if err != nil { logger.Fatal("no available port found", "preferred", port, "error", err) } if availablePort != port { logger.Info("port auto-switched", "from", port, "to", availablePort) } return availablePort } // Addr returns the actual server address (host:port) after resolvePort has been called. // Addr 返回调用 resolvePort 后的实际服务器地址(host:port)。 // // Returns: // - string: The server address, or empty string if server hasn't started. // // 返回值: // - string: 服务器地址,如果服务器尚未启动则返回空字符串。 func (s *Server) Addr() string { s.mu.RLock() defer s.mu.RUnlock() if s.srv == nil { return "" } return s.srv.Addr } // isUnix returns true if the current OS is Unix-like (Linux, macOS, BSD, etc.). // isUnix 如果当前操作系统是类 Unix 系统(Linux、macOS、BSD 等)则返回 true。 func isUnix() bool { return os.PathSeparator == '/' } // ErrReloadRequested is returned by Run() when a SIGHUP signal requests a reload. // ErrReloadRequested 当 SIGHUP 信号请求重载时由 Run() 返回。 // // The caller should typically exit with a non-zero code so the process supervisor // (systemd, Docker, k8s) can restart the application with the new config. // // 调用方通常应以非零退出码退出,以便进程管理器(systemd、Docker、k8s)可以使用新配置重启应用程序。 var ErrReloadRequested = errors.New("reload requested")