http.Server allows us to pass a context for the shutdown operation. This allows to us to use some of the context capabilities, such as cancelling and timeout. We can define a new server with its mux and a cancellable context:
mux := http.NewServeMux()
server := http.Server{
Addr: ":3000",
Handler: mux,
}
ctx, canc := context.WithCancel(context.Background())
defer canc()
mux.HandleFunc("/shutdown", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("OK"))
canc()
})
We can launch the server in a separate goroutine:
go func() {
if err := server.ListenAndServe(); err != nil {
if err != http.ErrServerClosed {
log.Fatal(err)
}
}
}()
The context will be complete when the shutdown endpoint is called and the cancellation function is invoked. We can wait for that event and then use another context with a timeout to call the shutdown method:
select {
case <-ctx.Done():
ctx, canc := context.WithTimeout(context.Background(), time.Second*5)
defer canc()
if err := server.Shutdown(ctx); err != nil {
log.Fatalln("Shutdown:", err)
} else {
log.Println("Shutdown:", "ok")
}
}
This will allow us to terminate the server effectively within the timeout, after which it will terminate with an error.