Gracefully shutdown a http server in golang

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")
}