package main import ( "context" "errors" "log" "net/http" "os" "os/signal" "strconv" "sync" "syscall" "time" ) type AppConfig struct { Addr string BadgerDir string PageSize int ShutdownTimeout time.Duration } func main() { logger := log.New(os.Stdout, "", log.LstdFlags|log.LUTC) if err := run(logger); err != nil { logger.Printf("server error: %v", err) os.Exit(1) } } func run(logger *log.Logger) error { cfg := loadConfig() if err := os.MkdirAll(cfg.BadgerDir, 0o755); err != nil { return err } store, err := OpenStore(cfg.BadgerDir) if err != nil { return err } var closeOnce sync.Once closeStore := func() { if err := store.Close(); err != nil { logger.Printf("close store: %v", err) } } defer closeOnce.Do(closeStore) app, err := NewApp(store, cfg.PageSize) if err != nil { return err } server := &http.Server{ Addr: cfg.Addr, Handler: app.Routes(), ReadHeaderTimeout: 5 * time.Second, ReadTimeout: 15 * time.Second, WriteTimeout: 30 * time.Second, IdleTimeout: 60 * time.Second, } ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer stop() go func() { <-ctx.Done() logger.Printf("shutdown signal received") shutdownCtx, cancel := context.WithTimeout(context.Background(), cfg.ShutdownTimeout) defer cancel() if err := server.Shutdown(shutdownCtx); err != nil { logger.Printf("http shutdown: %v", err) } closeOnce.Do(closeStore) }() logger.Printf("listening on %s", cfg.Addr) err = server.ListenAndServe() closeOnce.Do(closeStore) if err != nil && !errors.Is(err, http.ErrServerClosed) { return err } return nil } func loadConfig() AppConfig { return AppConfig{ Addr: envOrDefault("APP_ADDR", ":8080"), BadgerDir: envOrDefault("BADGER_DIR", "data/badger"), PageSize: envIntOrDefault("PAGE_SIZE", 50), ShutdownTimeout: envDurationOrDefault("SHUTDOWN_TIMEOUT", 10*time.Second), } } func envOrDefault(key, fallback string) string { if value := os.Getenv(key); value != "" { return value } return fallback } func envIntOrDefault(key string, fallback int) int { value := os.Getenv(key) if value == "" { return fallback } parsed, err := strconv.Atoi(value) if err != nil || parsed <= 0 { return fallback } return parsed } func envDurationOrDefault(key string, fallback time.Duration) time.Duration { value := os.Getenv(key) if value == "" { return fallback } parsed, err := time.ParseDuration(value) if err != nil || parsed <= 0 { return fallback } return parsed }