package mux import ( "context" "errors" "io" "log" "log/slog" "net" "net/http" "os/signal" "syscall" "time" ) type ServeCB func(srv *http.Server) error const ( shutdownDelay = time.Second * 10 shutdownHardDelay = time.Second * 5 drainDelay = time.Second ) // Serve with graceful shutdown func (m *Mux) Serve(cb ServeCB) { rootCtx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer stop() // catch all options // lets get it thorugh all middlewares m.OPTIONS("/", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Length", "0") if r.ContentLength != 0 { // Read up to 4KB of OPTIONS body (as mentioned in the // spec as being reserved for future use), but anything // over that is considered a waste of server resources // (or an attack) and we abort and close the connection, // courtesy of MaxBytesReader's EOF behavior. mb := http.MaxBytesReader(w, r.Body, 4<<10) io.Copy(io.Discard, mb) } }) srvCtx, cancelSrvCtx := context.WithCancel(context.Background()) srv := &http.Server{ Handler: m, BaseContext: func(_ net.Listener) context.Context { return srvCtx }, } go func() { if err := cb(srv); !errors.Is(err, http.ErrServerClosed) { panic(err) } }() // Wait for interrupt signal <-rootCtx.Done() stop() m.IsShuttingDown.Store(true) slog.Info("received interrupt singal, shutting down") time.Sleep(drainDelay) slog.Info("readiness check propagated, now waiting for ongoing requests to finish.") shutdownCtx, cancel := context.WithTimeout(context.Background(), shutdownDelay) defer cancel() err := srv.Shutdown(shutdownCtx) cancelSrvCtx() if err != nil { log.Println("failed to wait for ongoing requests to finish, waiting for forced cancellation") time.Sleep(shutdownHardDelay) } slog.Info("seerver shut down gracefully") }