From ae39203d95da4d94647b36cc1713276c6f216aa9 Mon Sep 17 00:00:00 2001 From: nuxen Date: Sat, 24 Feb 2024 18:57:45 +0100 Subject: [PATCH] feat(http): implement graceful shutdown --- cmd/seasonpackarr/main.go | 22 +++++++++++++++++++--- internal/http/middleware.go | 2 +- internal/http/server.go | 20 +++++++++++++++----- 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/cmd/seasonpackarr/main.go b/cmd/seasonpackarr/main.go index e422c6f..0e9c3e7 100644 --- a/cmd/seasonpackarr/main.go +++ b/cmd/seasonpackarr/main.go @@ -4,6 +4,7 @@ package main import ( + "context" "encoding/json" "fmt" netHTTP "net/http" @@ -125,16 +126,31 @@ func main() { errorChannel := make(chan error) go func() { - errorChannel <- srv.Open() + err := srv.Open() + if err != nil { + if !errors.Is(err, http.ErrServerClosed) { + errorChannel <- err + } + } }() sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM) - for sig := range sigCh { - log.Info().Msgf("received signal: %v, shutting down server.", sig) + select { + case sig := <-sigCh: + log.Info().Msgf("received signal: %q, shutting down server.", sig.String()) os.Exit(0) + + case err := <-errorChannel: + log.Error().Err(err).Msg("unexpected error from server") } + if err := srv.Shutdown(context.Background()); err != nil { + log.Error().Err(err).Msg("error during http shutdown") + os.Exit(1) + } + + os.Exit(0) default: pflag.Usage() diff --git a/internal/http/middleware.go b/internal/http/middleware.go index 0b280b7..b62bc72 100644 --- a/internal/http/middleware.go +++ b/internal/http/middleware.go @@ -6,7 +6,7 @@ package http import "net/http" -func (s Server) isAuthenticated(next http.Handler) http.Handler { +func (s *Server) isAuthenticated(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // allow access if apiToken value is set to an empty string if s.cfg.Config.APIToken == "" { diff --git a/internal/http/server.go b/internal/http/server.go index 6eb32ec..7568679 100644 --- a/internal/http/server.go +++ b/internal/http/server.go @@ -5,6 +5,7 @@ package http import ( + "context" "fmt" "net" "net/http" @@ -17,9 +18,13 @@ import ( "github.com/go-chi/chi/v5/middleware" ) +var ErrServerClosed = http.ErrServerClosed + type Server struct { log logger.Logger cfg *config.AppConfig + + httpServer http.Server } func NewServer(log logger.Logger, config *config.AppConfig) *Server { @@ -29,7 +34,7 @@ func NewServer(log logger.Logger, config *config.AppConfig) *Server { } } -func (s Server) Open() error { +func (s *Server) Open() error { addr := fmt.Sprintf("%v:%v", s.cfg.Config.Host, s.cfg.Config.Port) var err error @@ -44,7 +49,7 @@ func (s Server) Open() error { return err } -func (s Server) tryToServe(addr, proto string) error { +func (s *Server) tryToServe(addr, proto string) error { listener, err := net.Listen(proto, addr) if err != nil { return err @@ -52,15 +57,20 @@ func (s Server) tryToServe(addr, proto string) error { s.log.Info().Msgf("Starting %s server. Listening on %s", proto, listener.Addr().String()) - server := http.Server{ + s.httpServer = http.Server{ Handler: s.Handler(), ReadHeaderTimeout: time.Second * 15, } - return server.Serve(listener) + return s.httpServer.Serve(listener) +} + +func (s *Server) Shutdown(ctx context.Context) error { + s.log.Info().Msgf("shutting down http server gracefully...") + return s.httpServer.Shutdown(ctx) } -func (s Server) Handler() http.Handler { +func (s *Server) Handler() http.Handler { r := chi.NewRouter() r.Use(middleware.RequestID)