// Package server provides the HTTP server. package server import ( "context" "errors" "fmt" "log/slog" "net/http" "time" "github.com/go-chi/chi/v5" "go.uber.org/fx" "git.eeqj.de/sneak/upaas/internal/config" "git.eeqj.de/sneak/upaas/internal/globals" "git.eeqj.de/sneak/upaas/internal/handlers" "git.eeqj.de/sneak/upaas/internal/logger" "git.eeqj.de/sneak/upaas/internal/middleware" ) // Params contains dependencies for Server. type Params struct { fx.In Logger *logger.Logger Globals *globals.Globals Config *config.Config Middleware *middleware.Middleware Handlers *handlers.Handlers } // shutdownTimeout is how long to wait for graceful shutdown. const shutdownTimeout = 30 * time.Second // readHeaderTimeout is the maximum duration for reading request headers. const readHeaderTimeout = 10 * time.Second // Server is the HTTP server. type Server struct { startupTime time.Time port int log *slog.Logger router *chi.Mux httpServer *http.Server params Params mw *middleware.Middleware handlers *handlers.Handlers } // New creates a new Server instance. func New(lifecycle fx.Lifecycle, params Params) (*Server, error) { srv := &Server{ port: params.Config.Port, log: params.Logger.Get(), params: params, mw: params.Middleware, handlers: params.Handlers, } lifecycle.Append(fx.Hook{ OnStart: func(_ context.Context) error { srv.startupTime = time.Now() go srv.Run() return nil }, OnStop: func(ctx context.Context) error { return srv.Shutdown(ctx) }, }) return srv, nil } // Run starts the HTTP server. func (s *Server) Run() { s.SetupRoutes() listenAddr := fmt.Sprintf(":%d", s.port) s.httpServer = &http.Server{ Addr: listenAddr, Handler: s, ReadHeaderTimeout: readHeaderTimeout, } s.log.Info("http server starting", "addr", listenAddr) err := s.httpServer.ListenAndServe() if err != nil && !errors.Is(err, http.ErrServerClosed) { s.log.Error("http server error", "error", err) } } // Shutdown gracefully shuts down the server. func (s *Server) Shutdown(ctx context.Context) error { if s.httpServer == nil { return nil } s.log.Info("shutting down http server") shutdownCtx, cancel := context.WithTimeout(ctx, shutdownTimeout) defer cancel() err := s.httpServer.Shutdown(shutdownCtx) if err != nil { s.log.Error("http server shutdown error", "error", err) return fmt.Errorf("shutting down http server: %w", err) } s.log.Info("http server stopped") return nil } // ServeHTTP implements http.Handler. func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) { s.router.ServeHTTP(writer, request) }