64 lines
1.4 KiB
Go
64 lines
1.4 KiB
Go
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)
|
|
}
|
|
}
|