From d7ea0508cf776c5e1c22eaa548f53332670c50b3 Mon Sep 17 00:00:00 2001 From: Mike Bloy Date: Sun, 15 Sep 2024 00:51:18 -0500 Subject: [PATCH] move web server to goroutine --- cmd/root.go | 47 ++++++++++++++++++++++++----------------------- web/server.go | 31 +++++++++++++++++++------------ 2 files changed, 43 insertions(+), 35 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 3fc47a1..6990600 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,13 +1,13 @@ package cmd import ( + "context" "fmt" "log" "log/slog" - "math/rand" "os" - "strings" - "time" + "os/signal" + "syscall" "git.bloy.org/mike/hasshelper/web" "github.com/spf13/cobra" @@ -22,30 +22,32 @@ var rootCmd = &cobra.Command{ Run: rootCmdRun, } -func logLevelVar(str string) slog.Level { - levelUpper := strings.ToUpper(str) - switch levelUpper { - case "DEBUG": - return slog.LevelDebug - case "INFO": - return slog.LevelInfo - case "WARN": - return slog.LevelWarn - case "ERROR": - return slog.LevelError - default: - return slog.LevelInfo - } -} - func rootCmdRun(cmd *cobra.Command, args []string) { - logLevel := logLevelVar(viper.GetString("loglevel")) + var logLevel slog.Level + logLevel.UnmarshalText([]byte(viper.GetString("loglevel"))) logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: logLevel})) logger.Info("HASSHelper startup", "version", viper.GetString("version")) exitchan := make(chan bool) - web.Run(logger, exitchan) - <-exitchan // run the main command until one of the goroutines is done + signalchan := make(chan os.Signal, 1) + done := make(chan bool) + signal.Notify(signalchan, syscall.SIGINT, syscall.SIGTERM) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + web.Run(logger, exitchan, ctx) + go func() { + select { + case <-signalchan: + logger.Warn("received interrupt. Exiting") + cancel() + done <- true + case <-exitchan: + logger.Error("unexpected exit of component") + cancel() + done <- true + } + }() + <-done } // Execute will kick off cobra's processing of the root command @@ -100,5 +102,4 @@ func initConfig() { os.Exit(1) } } - rand.Seed(time.Now().UnixNano()) } diff --git a/web/server.go b/web/server.go index 5197e14..bb8151a 100644 --- a/web/server.go +++ b/web/server.go @@ -3,10 +3,12 @@ package web import ( "fmt" "log/slog" + "net" "net/http" "time" "github.com/spf13/viper" + "golang.org/x/net/context" ) type configObj struct { @@ -19,7 +21,7 @@ var config = configObj{} // Run starts up the webserver part of hasshelper. It writes to exitch if // the webserver ends unexpectedly. Config values will be read from viper. -func Run(rootLogger *slog.Logger, exitch chan bool) { +func Run(rootLogger *slog.Logger, exitch chan bool, ctx context.Context) { rootLogger = rootLogger.With("component", "web") config.logger = rootLogger config.port = viper.GetInt("webserver_port") @@ -30,15 +32,21 @@ func Run(rootLogger *slog.Logger, exitch chan bool) { var logger = config.logger logger.Info("Webserver startup", "port", config.port, "imageDir", config.imageDir) - - addr := fmt.Sprintf(":%d", config.port) - - if err := http.ListenAndServe(addr, middleware(http.DefaultServeMux)); err != nil { - logger.Error("Webserver fatal error", "err", err) - } else { - logger.Info("Webserver shutting down") - } - exitch <- true + go func() { + server := http.Server{ + Addr: fmt.Sprintf(":%d", config.port), + Handler: middleware(http.DefaultServeMux), + BaseContext: func(net.Listener) context.Context { + return ctx + }, + } + if err := server.ListenAndServe(); err != nil { + logger.Error("Webserver fatal error", "err", err) + } else { + logger.Info("Webserver shutting down") + } + exitch <- true + }() } func middleware(next http.Handler) http.Handler { @@ -46,8 +54,7 @@ func middleware(next http.Handler) http.Handler { logger := config.logger. WithGroup("request"). With("method", r.Method, - "url", r.URL, - "remote", r.RemoteAddr) + "url", r.URL) logger.Info("Starting web request") start := time.Now() next.ServeHTTP(w, r)