From c0349917de149cecefd79d0e032598cf8eaef9b1 Mon Sep 17 00:00:00 2001 From: Jan Tytgat Date: Tue, 29 Apr 2025 22:27:14 +0200 Subject: [PATCH] Refactor httpd functions Signed-off-by: Jan Tytgat --- httpd/httpd.go | 90 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 59 insertions(+), 31 deletions(-) diff --git a/httpd/httpd.go b/httpd/httpd.go index 6bfe57f..b12a3a3 100644 --- a/httpd/httpd.go +++ b/httpd/httpd.go @@ -3,12 +3,14 @@ package httpd import ( "context" "errors" + "fmt" "log/slog" + "net" "net/http" "strconv" "time" - "git.flexabyte.io/flexabyte/go-slogd/slogd" + "git.flexabyte.io/flexabyte/go-kit/slogd" ) func RunHttpServer(ctx context.Context, log *slog.Logger, listenAddress string, port int, h http.Handler, shutdownTimeout time.Duration) error { @@ -16,39 +18,14 @@ func RunHttpServer(ctx context.Context, log *slog.Logger, listenAddress string, Addr: listenAddress + ":" + strconv.Itoa(port), Handler: h} - return run(ctx, s, log, shutdownTimeout) -} + log.LogAttrs(ctx, slogd.LevelTrace, "starting http server", slog.String("listenAddress", fmt.Sprintf("http://%s", s.Addr))) -func RunSocketHttpServer(ctx context.Context, log *slog.Logger, socketPath string, h http.Handler, shutdownTimeout time.Duration) error { - s := &http.Server{ - Handler: h} + shutdownCtx, shutdownCancel := context.WithCancel(ctx) + defer shutdownCancel() - return run(ctx, s, log, shutdownTimeout) -} - -func run(ctx context.Context, s *http.Server, log *slog.Logger, shutdownTimeout time.Duration) error { - log.LogAttrs(ctx, slogd.LevelTrace, "starting http server", slog.String("listenAddress", s.Addr)) + // Run goroutine to handle graceful shutdown idleConnectionsClosed := make(chan struct{}) - - runCtx, runCancel := context.WithCancel(ctx) - defer runCancel() - - go func(ctx context.Context) { - log.LogAttrs(ctx, slogd.LevelTrace, "awaiting shutdown signal for http server", slog.String("listenAddress", s.Addr)) - <-ctx.Done() - - shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), shutdownTimeout) - defer shutdownCancel() - - log.LogAttrs(shutdownCtx, slogd.LevelTrace, "shutting down http server", slog.String("listenAddress", s.Addr)) - // We received an interrupt signal, shut down. - if err := s.Shutdown(shutdownCtx); err != nil { - // Error from closing listeners, or context timeout: - log.LogAttrs(ctx, slogd.LevelTrace, "shutting down http server failed", slog.String("listenAddress", s.Addr), slog.Any("error", err)) - } - log.LogAttrs(shutdownCtx, slogd.LevelTrace, "shutting down http server completed", slog.String("listenAddress", s.Addr)) - close(idleConnectionsClosed) - }(runCtx) + go shutdown(shutdownCtx, log, s, shutdownTimeout, idleConnectionsClosed) var err error if err = s.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) { @@ -60,3 +37,54 @@ func run(ctx context.Context, s *http.Server, log *slog.Logger, shutdownTimeout <-idleConnectionsClosed return err } + +func RunSocketHttpServer(ctx context.Context, log *slog.Logger, socketPath string, h http.Handler, shutdownTimeout time.Duration) error { + s := &http.Server{ + Handler: h} + + log.LogAttrs(ctx, slogd.LevelTrace, "starting http server", slog.String("socket", s.Addr)) + + shutdownCtx, shutdownCancel := context.WithCancel(ctx) + defer shutdownCancel() + + // Run goroutine to handle graceful shutdown + idleConnectionsClosed := make(chan struct{}) + go shutdown(shutdownCtx, log, s, shutdownTimeout, idleConnectionsClosed) + + var err error + var config = new(net.ListenConfig) + var socket net.Listener + + if socket, err = config.Listen(ctx, "unix", socketPath); err != nil { + log.LogAttrs(ctx, slogd.LevelError, "failed to listen on socket", slog.String("error", err.Error())) + return err + } + + if err = s.Serve(socket); err != nil && !errors.Is(err, http.ErrServerClosed) { + // Error starting or closing listener: + log.LogAttrs(ctx, slogd.LevelError, "http server start failed", slog.String("error", err.Error())) + return err + } + + <-idleConnectionsClosed + return err +} + +func shutdown(ctx context.Context, log *slog.Logger, s *http.Server, shutdownTimeout time.Duration, idleConnectionsClosed chan struct{}) { + log.LogAttrs(ctx, slogd.LevelTrace, "awaiting shutdown signal for http server", slog.String("listenAddress", s.Addr)) + <-ctx.Done() + + // When shutdown signal is received, create a new context with the configured shutdown timeout + shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), shutdownTimeout) + defer shutdownCancel() + + log.LogAttrs(shutdownCtx, slogd.LevelTrace, "shutdown signal received for http server", slog.String("listenAddress", s.Addr)) + time.Sleep(2 * time.Second) + // We received an interrupt signal, shut down. + if err := s.Shutdown(shutdownCtx); err != nil { + // Error from closing listeners, or context timeout: + log.LogAttrs(ctx, slogd.LevelTrace, "shutdown for http server failed", slog.String("listenAddress", s.Addr), slog.Any("error", err)) + } + log.LogAttrs(shutdownCtx, slogd.LevelTrace, "shutdown for http server completed", slog.String("listenAddress", s.Addr)) + close(idleConnectionsClosed) +}