Files
routewatch/internal/server/server.go
T
clawbot b305942e5e
check / check (push) Successful in 2m49s
Refuse invalid settings at start, health check follows PORT (closes #31)
PORT (1 to 65535) and XDG_DATA_HOME (an absolute path) are now checked in
the config package, which is built before the database opens, so a bad
value stops the start with an error and a non-zero exit instead of leaving
the daemon running without HTTP or putting the database somewhere else.
entrypoint.sh refuses a MALLOC_ARENA_MAX that is not a positive whole
number, since glibc ignores a bad one silently. The HEALTHCHECK probes the
port PORT names. The README corrects the Linux state directory, lists
XDG_DATA_HOME, and adds a "Running under upaas" section.

Model: opus-5-5
2026-09-28 17:05:36 +00:00

102 lines
2.4 KiB
Go

// Package server provides HTTP endpoints for status monitoring and statistics
package server
import (
"context"
"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 starts the HTTP server
func (s *Server) Start() error {
const (
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)
// Start in goroutine but log when actually listening
go func() {
s.logger.Info("HTTP server listening", "addr", s.srv.Addr)
if err := s.srv.ListenAndServe(); err != nil && 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
}