Initial scaffold with per-nameserver DNS monitoring model
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.
This commit is contained in:
43
internal/server/routes.go
Normal file
43
internal/server/routes.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
chimw "github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
)
|
||||
|
||||
// requestTimeout is the maximum duration for handling a request.
|
||||
const requestTimeout = 60 * time.Second
|
||||
|
||||
// SetupRoutes configures all HTTP routes.
|
||||
func (s *Server) SetupRoutes() {
|
||||
s.router = chi.NewRouter()
|
||||
|
||||
// Global middleware
|
||||
s.router.Use(chimw.Recoverer)
|
||||
s.router.Use(chimw.RequestID)
|
||||
s.router.Use(s.mw.Logging())
|
||||
s.router.Use(s.mw.CORS())
|
||||
s.router.Use(chimw.Timeout(requestTimeout))
|
||||
|
||||
// Health check
|
||||
s.router.Get("/health", s.handlers.HandleHealthCheck())
|
||||
|
||||
// API v1 routes
|
||||
s.router.Route("/api/v1", func(r chi.Router) {
|
||||
r.Get("/status", s.handlers.HandleStatus())
|
||||
})
|
||||
|
||||
// Metrics endpoint (optional, with basic auth)
|
||||
if s.params.Config.MetricsUsername != "" {
|
||||
s.router.Group(func(r chi.Router) {
|
||||
r.Use(s.mw.MetricsAuth())
|
||||
r.Get(
|
||||
"/metrics",
|
||||
promhttp.Handler().ServeHTTP,
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
129
internal/server/server.go
Normal file
129
internal/server/server.go
Normal file
@@ -0,0 +1,129 @@
|
||||
// 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)
|
||||
}
|
||||
Reference in New Issue
Block a user