package http import ( "context" "net" "net/http" "os" "os/signal" "sync" "syscall" "time" "github.com/influxdata/influxdb/v2/logger" "go.uber.org/zap" ) // DefaultShutdownTimeout is the default timeout for shutting down the http server. const DefaultShutdownTimeout = 20 * time.Second // Server is an abstraction around the http.Server that handles a server process. // It manages the full lifecycle of a server by serving a handler on a socket. // If signals have been registered, it will attempt to terminate the server using // Shutdown if a signal is received and will force a shutdown if a second signal // is received. type Server struct { ShutdownTimeout time.Duration srv *http.Server signals map[os.Signal]struct{} log *zap.Logger wg sync.WaitGroup } // NewServer returns a new server struct that can be used. func NewServer(log *zap.Logger, handler http.Handler) *Server { return &Server{ ShutdownTimeout: DefaultShutdownTimeout, srv: &http.Server{ Handler: handler, ErrorLog: zap.NewStdLog(log), }, log: log, } } // Serve will run the server using the listener to accept connections. func (s *Server) Serve(listener net.Listener) error { // When we return, wait for all pending goroutines to finish. defer s.wg.Wait() signalCh, cancel := s.notifyOnSignals() defer cancel() errCh := s.serve(listener) select { case err := <-errCh: // The server has failed and reported an error. return err case <-signalCh: // We have received an interrupt. Signal the shutdown process. return s.shutdown(signalCh) } } func (s *Server) serve(listener net.Listener) <-chan error { s.wg.Add(1) errCh := make(chan error, 1) go func() { defer s.wg.Done() if err := s.srv.Serve(listener); err != nil { errCh <- err } close(errCh) }() return errCh } func (s *Server) shutdown(signalCh <-chan os.Signal) error { s.log.Info("Shutting down server", logger.DurationLiteral("timeout", s.ShutdownTimeout)) // The shutdown needs to succeed in 20 seconds or less. ctx, cancel := context.WithTimeout(context.Background(), s.ShutdownTimeout) defer cancel() // Wait for another signal to cancel the shutdown. done := make(chan struct{}) defer close(done) s.wg.Add(1) go func() { defer s.wg.Done() select { case <-signalCh: s.log.Info("Initializing hard shutdown") cancel() case <-done: } }() return s.srv.Shutdown(ctx) } // ListenForSignals registers the the server to listen for the given signals // to shutdown the server. The signals are not captured until Serve is called. func (s *Server) ListenForSignals(signals ...os.Signal) { if s.signals == nil { s.signals = make(map[os.Signal]struct{}) } for _, sig := range signals { s.signals[sig] = struct{}{} } } func (s *Server) notifyOnSignals() (_ <-chan os.Signal, cancel func()) { if len(s.signals) == 0 { return nil, func() {} } // Retrieve which signals we want to be notified on. signals := make([]os.Signal, 0, len(s.signals)) for sig := range s.signals { signals = append(signals, sig) } // Create the signal channel and mark ourselves to be notified // of signals. Allow up to two signals for each signal type we catch. signalCh := make(chan os.Signal, len(signals)*2) signal.Notify(signalCh, signals...) return signalCh, func() { signal.Stop(signalCh) } } // ListenAndServe is a convenience method for opening a listener using the address // and then serving the handler on that address. This method sets up the typical // signal handlers. func ListenAndServe(log *zap.Logger, addr string, handler http.Handler) error { l, err := net.Listen("tcp", addr) if err != nil { return err } server := NewServer(log, handler) server.ListenForSignals(os.Interrupt, syscall.SIGTERM) return server.Serve(l) }