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
// context: wait for 3 seconds ctx, cancel := context.WithTimeout( context.Background(), 3*time.Second)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: