Full project structure following upaas conventions: uber/fx DI, go-chi routing, slog logging, Viper config. State persisted as JSON file with per-nameserver record tracking for inconsistency detection. Stub implementations for resolver, portcheck, tlscheck, and watcher.
130 lines
2.7 KiB
Go
130 lines
2.7 KiB
Go
// 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"
|
|
|
|
"sneak.berlin/go/dnswatcher/internal/config"
|
|
"sneak.berlin/go/dnswatcher/internal/globals"
|
|
"sneak.berlin/go/dnswatcher/internal/handlers"
|
|
"sneak.berlin/go/dnswatcher/internal/logger"
|
|
"sneak.berlin/go/dnswatcher/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 max 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)
|
|
}
|