finish
A non-intrusive package, adding a graceful shutdown to Go's HTTP server, by
utilizing http.Server
's built-in Shutdown()
method, with zero dependencies.
Quick Start
Assume the following code in a file called simple.go
:
package main
import (
"fmt"
"log"
"net/http"
"time"
"github.com/pseidemann/finish"
)
func main() {
http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
time.Sleep(5 * time.Second)
fmt.Fprintln(w, "world")
})
srv := &http.Server{Addr: "localhost:8080"}
fin := finish.New()
fin.Add(srv)
go func() {
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
log.Fatal(err)
}
}()
fin.Wait()
}
Now execute that file:
$ go run simple.go
Do a HTTP GET request:
$ curl localhost:8080/hello
This will print "world" after 5 seconds.
When the server is terminated with pressing Ctrl+C
or kill
, while /hello
is
loading, finish will wait until the request was handled, before the server gets
killed.
The output will look like this:
2038/01/19 03:14:08 finish: shutdown signal received
2038/01/19 03:14:08 finish: shutting down server ...
2038/01/19 03:14:11 finish: server closed
Customization
Change Timeout
How to change the default timeout of 10 seconds.
package main
import (
"fmt"
"log"
"net/http"
"time"
"github.com/pseidemann/finish"
)
func main() {
http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
time.Sleep(5 * time.Second)
fmt.Fprintln(w, "world")
})
srv := &http.Server{Addr: "localhost:8080"}
fin := &finish.Finisher{Timeout: 30 * time.Second}
fin.Add(srv)
go func() {
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
log.Fatal(err)
}
}()
fin.Wait()
}
In this example the timeout is set to 30 seconds.
Change Logger
How to change the default logger.
package main
import (
"fmt"
"log"
"net/http"
"time"
"github.com/pseidemann/finish"
"github.com/sirupsen/logrus"
)
func main() {
http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
time.Sleep(5 * time.Second)
fmt.Fprintln(w, "world")
})
srv := &http.Server{Addr: "localhost:8080"}
fin := &finish.Finisher{Log: logrus.StandardLogger()}
fin.Add(srv)
go func() {
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
log.Fatal(err)
}
}()
fin.Wait()
}
In this example, logrus is configured for logging.
Change Signals
How to change the default signals (SIGINT
, SIGTERM
) which will initiate the shutdown.
package main
import (
"fmt"
"log"
"net/http"
"syscall"
"time"
"github.com/pseidemann/finish"
)
func main() {
http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
time.Sleep(5 * time.Second)
fmt.Fprintln(w, "world")
})
srv := &http.Server{Addr: "localhost:8080"}
fin := &finish.Finisher{Signals: append(finish.DefaultSignals, syscall.SIGHUP)}
fin.Add(srv)
go func() {
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
log.Fatal(err)
}
}()
fin.Wait()
}
In this example, finish will not only catch the default signals SIGINT
and SIGTERM
but also the signal SIGHUP
.
Full Example
This example uses a custom router httprouter,
a different timeout, a custom logger logrus,
custom signals, options for Add()
and multiple servers.
package main
import (
"fmt"
"log"
"net/http"
"syscall"
"time"
"github.com/julienschmidt/httprouter"
"github.com/pseidemann/finish"
"github.com/sirupsen/logrus"
)
func main() {
routerPub := httprouter.New()
routerPub.HandlerFunc("GET", "/hello", func(w http.ResponseWriter, r *http.Request) {
time.Sleep(5 * time.Second)
fmt.Fprintln(w, "world")
})
routerInt := httprouter.New()
routerInt.HandlerFunc("GET", "/status", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "ok")
})
srvPub := &http.Server{Addr: "localhost:8080", Handler: routerPub}
srvInt := &http.Server{Addr: "localhost:3000", Handler: routerInt}
fin := &finish.Finisher{
Timeout: 30 * time.Second,
Log: logrus.StandardLogger(),
Signals: append(finish.DefaultSignals, syscall.SIGHUP),
}
fin.Add(srvPub, finish.WithName("public server"))
fin.Add(srvInt, finish.WithName("internal server"), finish.WithTimeout(5*time.Second))
go func() {
logrus.Infof("starting public server at %s", srvPub.Addr)
if err := srvPub.ListenAndServe(); err != http.ErrServerClosed {
log.Fatal(err)
}
}()
go func() {
logrus.Infof("starting internal server at %s", srvInt.Addr)
if err := srvInt.ListenAndServe(); err != http.ErrServerClosed {
log.Fatal(err)
}
}()
fin.Wait()
}