Cybersecurity-Projects/PROJECTS/beginner/canary-token-generator/backend/internal/server/server.go

109 lines
2.4 KiB
Go

// ©AngelaMos | 2026
// server.go
package server
import (
"context"
"errors"
"fmt"
"log/slog"
"net/http"
"time"
"github.com/go-chi/chi/v5"
chimw "github.com/go-chi/chi/v5/middleware"
"github.com/CarterPerez-dev/cybersecurity-projects/canary-token-generator/backend/internal/config"
"github.com/CarterPerez-dev/cybersecurity-projects/canary-token-generator/backend/internal/health"
)
type Server struct {
httpServer *http.Server
router *chi.Mux
config config.ServerConfig
healthHandler *health.Handler
logger *slog.Logger
}
type Config struct {
ServerConfig config.ServerConfig
HealthHandler *health.Handler
Logger *slog.Logger
}
func New(cfg Config) *Server {
router := chi.NewRouter()
router.Use(chimw.CleanPath)
router.Use(chimw.StripSlashes)
return &Server{
httpServer: &http.Server{
Addr: cfg.ServerConfig.Address(),
Handler: router,
ReadTimeout: cfg.ServerConfig.ReadTimeout,
WriteTimeout: cfg.ServerConfig.WriteTimeout,
IdleTimeout: cfg.ServerConfig.IdleTimeout,
},
router: router,
config: cfg.ServerConfig,
healthHandler: cfg.HealthHandler,
logger: cfg.Logger,
}
}
func (s *Server) Router() *chi.Mux {
return s.router
}
func (s *Server) Start() error {
s.logger.Info("starting HTTP server",
"addr", s.config.Address(),
"read_timeout", s.config.ReadTimeout,
"write_timeout", s.config.WriteTimeout,
"idle_timeout", s.config.IdleTimeout,
)
if err := s.httpServer.ListenAndServe(); err != nil &&
!errors.Is(err, http.ErrServerClosed) {
return fmt.Errorf("http server error: %w", err)
}
return nil
}
func (s *Server) Shutdown(ctx context.Context, drainDelay time.Duration) error {
s.logger.Info("initiating graceful shutdown")
s.logger.Info("marking server as not ready")
if s.healthHandler != nil {
s.healthHandler.SetReady(false)
s.healthHandler.SetShutdown(true)
}
s.logger.Info("waiting for load balancer to drain",
"delay", drainDelay,
)
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(drainDelay):
}
s.logger.Info("stopping HTTP server")
shutdownCtx, cancel := context.WithTimeout(ctx, s.config.ShutdownTimeout)
defer cancel()
if err := s.httpServer.Shutdown(shutdownCtx); err != nil {
return fmt.Errorf("http server shutdown: %w", err)
}
s.logger.Info("HTTP server stopped gracefully")
return nil
}
func (s *Server) Address() string {
return s.httpServer.Addr
}