diff --git a/httpserver.go b/httpserver.go index dce277a..e0481ea 100644 --- a/httpserver.go +++ b/httpserver.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "net" "net/http" "time" @@ -80,7 +81,10 @@ func (s *httpServer) OnInit() error { return nil } -// OnStart starts the HTTP server in a background goroutine. +// OnStart binds the TCP listener synchronously so that a port conflict returns +// an error immediately — allowing the launcher to trigger a clean shutdown +// instead of running silently without an HTTP server. The accepted connections +// are then served in a background goroutine. func (s *httpServer) OnStart() error { host := s.cfg.Host if host == "" { @@ -90,16 +94,23 @@ func (s *httpServer) OnStart() error { if port == 0 { port = 8080 } + addr := fmt.Sprintf("%s:%d", host, port) + + ln, err := net.Listen("tcp", addr) + if err != nil { + return fmt.Errorf("httpserver: bind %s: %w", addr, err) + } + s.srv = &http.Server{ - Addr: fmt.Sprintf("%s:%d", host, port), + Addr: addr, Handler: s.Router, ReadTimeout: s.cfg.ReadTimeout, WriteTimeout: s.cfg.WriteTimeout, IdleTimeout: s.cfg.IdleTimeout, } - s.logger.Info("httpserver: starting", "addr", s.srv.Addr) + s.logger.Info("httpserver: starting", "addr", addr) go func() { - if err := s.srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + if err := s.srv.Serve(ln); err != nil && !errors.Is(err, http.ErrServerClosed) { s.logger.Error("httpserver: fatal error", err) } }()