To gracefully shutdown a http server requires a few steps.
The http library's serving function will block over there when getting started, until we call http.Server.Close(). As for us, we can invoke the starting function in a goroutine, and call close in another one.
Here is a full simple example:
package main import ( "context" "fmt" "github.com/argcv/stork/log" "github.com/gin-gonic/gin" "github.com/pkg/errors" "net/http" "os" "os/signal" "sync" "time" ) type HttpSrv struct { Port int server *http.Server isStarted bool mtx *sync.Mutex } func NewHttpSrv(port int) *HttpSrv { return &HttpSrv{ Port: port, server: nil, isStarted: false, mtx: &sync.Mutex{}, } } func (srv *HttpSrv) Start() (err error) { srv.mtx.Lock() defer srv.mtx.Unlock() if srv.isStarted { return errors.New("Server is already started") } srv.isStarted = true // prepare router router := gin.Default() router.GET("/*uri", func(c *gin.Context) { uri := c.Param("uri") c.JSON(200, map[string]interface{}{ "status": "ok", "uri": uri, }) }) // prepare address addr := fmt.Sprintf(":%v", srv.Port) // prepare http server srv.server = &http.Server{ Addr: addr, Handler: router, } log.Infof("Starting http server: %v", srv) go func() { if err = srv.server.ListenAndServe(); err != nil { if err == http.ErrServerClosed { log.Infof("Server closed under request: %v", err) } else { log.Fatalf("Server closed unexpect: %v", err) } } // in case of closed normally srv.isStarted = false }() time.Sleep(10 * time.Millisecond) return } func (m *HttpSrv) Shutdown(ctx context.Context) (err error) { m.mtx.Lock() defer m.mtx.Unlock() if !m.isStarted || m.server == nil { return errors.New("Server is not started") } stop := make(chan bool) go func() { // dummy preprocess before interrupted //time.Sleep(4 * time.Second) // Close immediately closes all active net.Listeners and any // connections in state StateNew, StateActive, or StateIdle. For a // graceful shutdown, use Shutdown. // // Close does not attempt to close (and does not even know about) // any hijacked connections, such as WebSockets. // // Close returns any error returned from closing the Server's // underlying Listener(s). //err = m.server.Close() // We can use .Shutdown to gracefully shuts down the server without // interrupting any active connection err = m.server.Shutdown(ctx) stop <- true }() select { case <-ctx.Done(): log.Errorf("Timeout: %v", ctx.Err()) break case <-stop: log.Infof("Finished") } return } func main() { srv := NewHttpSrv(9990) if err := srv.Start(); err != nil { log.Fatalf("Start failed: %v", err) return } quit := make(chan os.Signal) signal.Notify(quit, os.Interrupt) // holding here // waiting for a interrupt signal <-quit log.Infof("[Control-C] Get signal: shutdown server ...") signal.Reset(os.Interrupt) // starting shutting down progress... log.Infof("Server shutting down") // context: wait for 3 seconds ctx, cancel := context.WithTimeout( context.Background(), 3*time.Second) defer cancel() // call for shutdown if err := srv.Shutdown(ctx); err != nil { log.Errorf("Server Shutdown failed: %v", err) } log.Infof("Server exiting") }
2 Comments
Piero · May 12, 2019 at 10:37
Why 3 seconds?
What happen with connections if they are still open?
there is a way to wait until all http connections are closed?
Yu · May 12, 2019 at 12:39
This line will generate a context with a deadline ( https://golang.org/pkg/context/#WithTimeout ), and pass to `srv.Shutdown(ctx)`. It will be effected in the select {} statement and return directly after 3 seconds.
What should be noticed is the `time.Sleep(4 * time.Second)` in the `Shutdown` statement was intentionally used to generate a timeout and the `http.Server.Close()` will close all the connections immediately.
I just update the code and removed the sleep, and called `http.ServerShutdown(ctx)` instead. If you really wish to use it to handle some real requests, please use this one.
In generally an HTTP request should never execute too long time, hundreds of milliseconds is already to be a very dangerous behavior. I am afraid 3 seconds is a long enough time here. If you wish to “wait until all http connections are closed”, that’s easy, you can use the default ctx instead: