package main import ( "context" "errors" "fmt" "log" "net/http" "os/signal" "syscall" "time" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" ) func main() { // The HTTP Server server := &http.Server{Addr: "0.0.0.0:3333", Handler: service()} // Create context that listens for the interrupt signal ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer stop() // Run server in the background go func() { if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { log.Fatal(err) } }() // Listen for the interrupt signal <-ctx.Done() // Create shutdown context with 30-second timeout shutdownCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() // Trigger graceful shutdown if err := server.Shutdown(shutdownCtx); err != nil { log.Fatal(err) } } func service() http.Handler { r := chi.NewRouter() r.Use(middleware.RequestID) r.Use(middleware.Logger) r.Get("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("sup")) }) r.Get("/slow", func(w http.ResponseWriter, r *http.Request) { // Simulates some hard work. // // We want this handler to complete successfully during a shutdown signal, // so consider the work here as some background routine to fetch a long-running // search query to find as many results as possible, but, instead we cut it short // and respond with what we have so far. How a shutdown is handled is entirely // up to the developer, as some code blocks are preemptible, and others are not. time.Sleep(5 * time.Second) w.Write([]byte(fmt.Sprintf("all done.\n"))) }) return r }