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")
}
Comments · 2