package server import ( "context" "io/fs" "log/slog" "net/http" "time" ) type Server struct { httpServer *http.Server log *slog.Logger } // New creates an HTTP server that serves the embedded frontend and a health // check endpoint. Additional routes (API, WebSocket) will be registered in // later phases. func New(addr string, webFS fs.FS, log *slog.Logger) *Server { mux := http.NewServeMux() // Health check mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte("ok")) }) // Serve embedded frontend for everything else mux.Handle("/", http.FileServerFS(webFS)) return &Server{ httpServer: &http.Server{ Addr: addr, Handler: mux, ReadTimeout: 10 * time.Second, WriteTimeout: 30 * time.Second, IdleTimeout: 120 * time.Second, }, log: log, } } // Start listens and serves until ctx is cancelled. func (s *Server) Start(ctx context.Context) error { s.log.Info("listening", "addr", s.httpServer.Addr) errCh := make(chan error, 1) go func() { if err := s.httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { errCh <- err } }() select { case err := <-errCh: return err case <-ctx.Done(): shutCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() s.log.Info("shutting down") return s.httpServer.Shutdown(shutCtx) } }