package main import ( "context" "errors" "log/slog" "net/http" "os" "os/signal" "syscall" "ipecho/internal/buildinfo" "ipecho/internal/clientip" "ipecho/internal/config" "ipecho/internal/httpapi" ) func main() { cfg, err := config.Load() if err != nil { logger := slog.New(slog.NewTextHandler(os.Stderr, nil)) logger.Error("invalid configuration", "error", err) os.Exit(1) } logger := newLogger(cfg) server := &http.Server{ Addr: cfg.Addr, Handler: httpapi.NewWithLogger(clientip.Resolver{ TrustProxyHeaders: cfg.TrustProxyHeaders, TrustedProxies: cfg.TrustedProxyCIDRs, }, logger), ReadHeaderTimeout: cfg.ReadHeaderTimeout, ReadTimeout: cfg.ReadTimeout, WriteTimeout: cfg.WriteTimeout, IdleTimeout: cfg.IdleTimeout, MaxHeaderBytes: cfg.MaxHeaderBytes, } ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer stop() serverErrors := make(chan error, 1) go func() { logger.Info("server started", "addr", cfg.Addr, "trust_proxy_headers", cfg.TrustProxyHeaders, "trusted_proxy_cidrs", cfg.TrustedProxyCIDRs, "version", buildinfo.Version, "commit", buildinfo.Commit, "date", buildinfo.Date, ) serverErrors <- server.ListenAndServe() }() select { case <-ctx.Done(): logger.Info("server stopping") case err := <-serverErrors: if !errors.Is(err, http.ErrServerClosed) { logger.Error("server stopped unexpectedly", "error", err) os.Exit(1) } return } shutdownCtx, cancel := context.WithTimeout(context.Background(), cfg.ShutdownTimeout) defer cancel() if err := server.Shutdown(shutdownCtx); err != nil { logger.Error("server shutdown failed", "error", err) os.Exit(1) } } func newLogger(cfg config.Config) *slog.Logger { options := &slog.HandlerOptions{ Level: cfg.LogLevel, } if cfg.LogFormat == "json" { return slog.New(slog.NewJSONHandler(os.Stdout, options)) } return slog.New(slog.NewTextHandler(os.Stdout, options)) }