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:

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