// Package server provides HTTP endpoints for status monitoring and statistics package server import ( "context" "errors" "fmt" "net" "net/http" "strconv" "time" "git.eeqj.de/sneak/routewatch/internal/config" "git.eeqj.de/sneak/routewatch/internal/database" "git.eeqj.de/sneak/routewatch/internal/logger" "git.eeqj.de/sneak/routewatch/internal/streamer" "github.com/go-chi/chi/v5" ) // ASNFetcherStats contains WHOIS fetcher statistics. type ASNFetcherStats struct { SuccessesLastHour int ErrorsLastHour int CurrentInterval time.Duration ConsecutiveFails int } // ASNFetcher is an interface for queuing ASN WHOIS lookups. type ASNFetcher interface { QueueImmediate(asn int) GetStats() ASNFetcherStats } // Server provides HTTP endpoints for status monitoring type Server struct { router *chi.Mux db database.Store streamer *streamer.Streamer logger *logger.Logger port int srv *http.Server asnFetcher ASNFetcher } // New creates a new HTTP server func New( db database.Store, streamer *streamer.Streamer, logger *logger.Logger, cfg *config.Config, ) *Server { s := &Server{ db: db, streamer: streamer, logger: logger, port: cfg.Port, } s.setupRoutes() return s } // Start listens on the configured port and serves HTTP in the background. // It returns an error if it cannot listen, for example because the port is // already in use. ctx bounds only opening the port, not serving. func (s *Server) Start(ctx context.Context) error { const ( network = "tcp" readHeaderTimeout = 40 * time.Second readTimeout = 60 * time.Second writeTimeout = 60 * time.Second idleTimeout = 120 * time.Second ) s.srv = &http.Server{ Addr: ":" + strconv.Itoa(s.port), Handler: s.router, ReadHeaderTimeout: readHeaderTimeout, ReadTimeout: readTimeout, WriteTimeout: writeTimeout, IdleTimeout: idleTimeout, } s.logger.Info("Starting HTTP server", "port", s.port, "addr", s.srv.Addr) var listenConfig net.ListenConfig listener, err := listenConfig.Listen(ctx, network, s.srv.Addr) if err != nil { return fmt.Errorf("starting HTTP server: %w", err) } s.logger.Info("HTTP server listening", "addr", listener.Addr().String()) go func() { err := s.srv.Serve(listener) if err != nil && !errors.Is(err, http.ErrServerClosed) { s.logger.Error("HTTP server error", "error", err) } }() return nil } // Stop gracefully stops the HTTP server func (s *Server) Stop(ctx context.Context) error { if s.srv == nil { return nil } s.logger.Info("Stopping HTTP server") return s.srv.Shutdown(ctx) } // SetASNFetcher sets the ASN WHOIS fetcher for on-demand lookups. func (s *Server) SetASNFetcher(fetcher ASNFetcher) { s.asnFetcher = fetcher }