// Package app 提供 Lolly 应用程序的生命周期管理和命令行入口。 // // 包含应用通用逻辑相关的工具函数。 // // 作者:xfy package app import ( "fmt" "net" "os" "sync" "time" "rua.plus/lolly/internal/config" "rua.plus/lolly/internal/http2" "rua.plus/lolly/internal/http3" "rua.plus/lolly/internal/logging" "rua.plus/lolly/internal/resolver" "rua.plus/lolly/internal/server" "rua.plus/lolly/internal/stream" "rua.plus/lolly/internal/variable" ) // App manages the server lifecycle, including HTTP, HTTP/3, Stream servers and graceful upgrades. type App struct { resv resolver.Resolver cfg *config.Config srv *server.Server http3Srv *http3.Server http2Srv *http2.Server streamSrv *stream.Server upgradeMgr *server.UpgradeManager logger *logging.AppLogger cfgPath string pidFile string logFile string listeners []net.Listener ready chan struct{} readyOnce sync.Once } // NewApp creates a new App instance with the given config path. func NewApp(cfgPath string) *App { return &App{ cfgPath: cfgPath, ready: make(chan struct{}), } } // WaitReady blocks until the app has completed initialization. func (a *App) WaitReady() { <-a.ready } // signalReady closes the ready channel to unblock callers of WaitReady. func (a *App) signalReady() { a.readyOnce.Do(func() { close(a.ready) }) } // SetPidFile sets the path to the PID file for the app. func (a *App) SetPidFile(path string) { a.pidFile = path } // SetLogFile sets the path to the log file for the app. func (a *App) SetLogFile(path string) { a.logFile = path } // loadAndValidateConfig loads configuration and initializes the logger. func (a *App) loadAndValidateConfig() error { cfg, err := config.Load(a.cfgPath) if err != nil { fmt.Fprintf(os.Stderr, "Failed to load config: %v\n", err) return err } a.cfg = cfg a.logger = logging.NewAppLogger(&cfg.Logging) return nil } // initVariables loads global variables from configuration. func (a *App) initVariables() { variable.SetGlobalVariables(a.cfg.Variables.Set) if len(a.cfg.Variables.Set) > 0 { a.logger.LogStartup("Global variables loaded", map[string]string{ "count": fmt.Sprintf("%d", len(a.cfg.Variables.Set)), }) } } // logServerAddresses logs the listening addresses based on server mode. func (a *App) logServerAddresses() { a.logger.LogStartup("Config loaded successfully", map[string]string{"config_path": a.cfgPath}) if len(a.cfg.Servers) == 0 { return } mode := a.cfg.GetMode() if mode == config.ServerModeMultiServer { for i, srv := range a.cfg.Servers { a.logger.LogStartup("Listening address", map[string]string{ "index": fmt.Sprintf("[%d]", i), "listen": srv.Listen, "name": srv.Name, }) } } else { a.logger.LogStartup("Listening address", map[string]string{"listen": a.cfg.Servers[0].Listen}) } } // initResolver initializes the DNS resolver if enabled. func (a *App) initResolver() { if a.cfg.Resolver.Enabled { a.resv = resolver.New(&a.cfg.Resolver) a.logger.LogStartup("DNS resolver enabled", map[string]string{ "addresses": fmt.Sprintf("%v", a.cfg.Resolver.Addresses), "ttl": a.cfg.Resolver.TTL().String(), }) } } // initServer creates the main server and sets the resolver. func (a *App) initServer() { a.srv = server.New(a.cfg) if a.resv != nil { a.srv.SetResolver(a.resv) } if len(a.listeners) > 0 { a.srv.SetListeners(a.listeners) } } // initStreamServers configures and starts stream servers. func (a *App) initStreamServers() { if len(a.cfg.Stream) == 0 { return } a.streamSrv = stream.NewServer() for _, sc := range a.cfg.Stream { targets := make([]stream.TargetSpec, len(sc.Upstream.Targets)) for i, t := range sc.Upstream.Targets { targets[i] = stream.TargetSpec{ Addr: t.Addr, Weight: t.Weight, } } if err := a.streamSrv.AddUpstream(sc.Listen, targets, sc.Upstream.LoadBalance, stream.HealthCheckSpec{}); err != nil { a.logger.Error().Err(err).Msg("Failed to add Stream upstream") } if sc.Protocol == "udp" { if err := a.streamSrv.ListenUDP(sc.Listen, sc.Listen, 60*time.Second); err != nil { a.logger.Error().Err(err).Str("listen", sc.Listen).Msg("Failed to listen on UDP") } } else { if err := a.streamSrv.ListenTCP(sc.Listen); err != nil { a.logger.Error().Err(err).Str("listen", sc.Listen).Msg("Failed to listen on TCP") } } } go func() { a.logger.LogStartup("Starting Stream server", nil) if err := a.streamSrv.Start(); err != nil { a.logger.Error().Err(err).Msg("Stream server failed to start") } }() } // initHTTP3 starts the HTTP/3 server if enabled. func (a *App) initHTTP3() { if len(a.cfg.Servers) == 0 || !a.cfg.HTTP3.Enabled || a.cfg.Servers[0].SSL.Cert == "" { return } tlsConfig, err := a.srv.GetTLSConfig() if err != nil { a.logger.Error().Err(err).Msg("Failed to get TLS config, skipping HTTP/3") return } a.http3Srv, err = http3.NewServer(&a.cfg.HTTP3, a.srv.GetHandler(), tlsConfig) if err != nil { a.logger.Error().Err(err).Msg("Failed to create HTTP/3 server") return } go func() { a.logger.LogStartup("Starting HTTP/3 server", map[string]string{"listen": a.cfg.HTTP3.Listen}) if err := a.http3Srv.Start(); err != nil { a.logger.Error().Err(err).Msg("HTTP/3 server failed to start") } }() } // initHTTP2 starts the HTTP/2 server if enabled. func (a *App) initHTTP2() { if len(a.cfg.Servers) == 0 || !a.cfg.Servers[0].SSL.HTTP2.Enabled || a.cfg.Servers[0].SSL.Cert == "" { return } tlsConfig, err := a.srv.GetTLSConfig() if err != nil { a.logger.Error().Err(err).Msg("Failed to get TLS config, skipping HTTP/2") return } a.http2Srv, err = http2.NewServer(&a.cfg.Servers[0].SSL.HTTP2, a.srv.GetHandler(), tlsConfig) if err != nil { a.logger.Error().Err(err).Msg("Failed to create HTTP/2 server") return } go func() { a.logger.LogStartup("Starting HTTP/2 server", map[string]string{ "listen": a.cfg.Servers[0].Listen, "max_concurrent_streams": fmt.Sprintf("%d", a.cfg.Servers[0].SSL.HTTP2.MaxConcurrentStreams), "push_enabled": fmt.Sprintf("%t", a.cfg.Servers[0].SSL.HTTP2.PushEnabled), }) // HTTP/2 shares the main server's listener; ALPN negotiates protocol selection. listeners := a.srv.GetListeners() if len(listeners) > 0 { if err := a.http2Srv.Serve(listeners[0]); err != nil { a.logger.Error().Err(err).Msg("HTTP/2 server failed to start") } } else { a.logger.Error().Msg("HTTP/2 server failed to start: no available listeners") } }() } // shutdownHTTP3 gracefully stops the HTTP/3 server. func (a *App) shutdownHTTP3() { if a.http3Srv != nil { if err := a.http3Srv.Stop(); err != nil { a.logger.Error().Err(err).Msg("Failed to shutdown HTTP/3 server") } } } // shutdownHTTP2 gracefully stops the HTTP/2 server. func (a *App) shutdownHTTP2() { if a.http2Srv != nil { if err := a.http2Srv.Stop(); err != nil { a.logger.Error().Err(err).Msg("Failed to shutdown HTTP/2 server") } } } // reopenLogs reinitializes the logger from current config. func (a *App) shutdownStream() { if a.streamSrv != nil { a.streamSrv.Stop() } } func (a *App) reopenLogs() { if a.cfg != nil { logging.Init(a.cfg.Logging.Error.Level, a.cfg.Logging.Format) a.logger = logging.NewAppLogger(&a.cfg.Logging) } a.logger.LogStartup("Logs reopened", nil) }