finish

package module
v1.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 10, 2021 License: BSD-3-Clause Imports: 8 Imported by: 0

README

finish

GoDoc Go Report Card Build Status

A non-intrusive package adding a graceful shutdown to Go's HTTP server by utilizing http.Server's built-in Shutdown() method.

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() {
		err := srv.ListenAndServe()
		if 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() {
		err := srv.ListenAndServe()
		if 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() {
		err := srv.ListenAndServe()
		if 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() {
		err := srv.ListenAndServe()
		if 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)
		err := srvPub.ListenAndServe()
		if err != http.ErrServerClosed {
			log.Fatal(err)
		}
	}()

	go func() {
		logrus.Infof("starting internal server at %s", srvInt.Addr)
		err := srvInt.ListenAndServe()
		if err != http.ErrServerClosed {
			log.Fatal(err)
		}
	}()

	fin.Wait()
}

Documentation

Overview

Package finish adds a graceful shutdown to Go's HTTP server.

It utilizes http.Server's built-in Shutdown() method.

Example
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() {
		err := srv.ListenAndServe()
		if err != http.ErrServerClosed {
			log.Fatal(err)
		}
	}()

	fin.Wait()
}
Output:

Index

Examples

Constants

View Source
const DefaultTimeout = 10 * time.Second

DefaultTimeout is used if Finisher.Timeout is not set.

Variables

View Source
var (
	// DefaultLogger is used if Finisher.Logger is not set. It uses the Go standard log package.
	DefaultLogger = &defaultLogger{}
	// StdoutLogger can be used as a simple logger which writes to stdout via the fmt standard package.
	StdoutLogger = &stdoutLogger{}
	// DefaultSignals is used if Finisher.Signals is not set.
	// The default shutdown signals are:
	//   - SIGINT (triggered by pressing Control-C)
	//   - SIGTERM (sent by `kill $pid` or e.g. systemd stop)
	DefaultSignals = []os.Signal{syscall.SIGINT, syscall.SIGTERM}
)

Functions

This section is empty.

Types

type Finisher

type Finisher struct {
	// Timeout is the maximum amount of time to wait for still running server
	// requests to finish when the shutdown signal was received for each server.
	// It defaults to DefaultTimeout which is 10 seconds.
	//
	// The timeout can be overridden on a per server basis with passing the
	// WithTimeout() option to Add() while adding the server.
	Timeout time.Duration

	// Log can be set to change where finish logs to.
	// It defaults to DefaultLogger which uses the standard Go log package.
	Log Logger

	// Signals can be used to change which signals finish catches to initiate
	// the shutdown.
	// It defaults to DefaultSignals which contains SIGINT and SIGTERM.
	Signals []os.Signal
	// contains filtered or unexported fields
}

Finisher implements graceful shutdown of servers.

func New

func New() *Finisher

New creates a Finisher. This is a convenience constructor if no changes to the default configuration are needed.

func (*Finisher) Add

func (f *Finisher) Add(srv Server, opts ...Option)

Add a server for graceful shutdown.

Options can be passed as the second argument to change the behavior for this server:

To give the server a specific name instead of just "server #<num>":

fin.Add(srv, finish.WithName("internal server"))

To override the timeout, configured in Finisher, for this specific server:

fin.Add(srv, finish.WithTimeout(5*time.Second))

To do both at the same time:

fin.Add(srv, finish.WithName("internal server"), finish.WithTimeout(5*time.Second))

func (*Finisher) Trigger

func (f *Finisher) Trigger()

Trigger the shutdown signal manually. This is probably only useful for testing.

func (*Finisher) Wait

func (f *Finisher) Wait()

Wait blocks until one of the shutdown signals is received and then closes all servers with a timeout.

type Logger

type Logger interface {
	Infof(string, ...interface{})
	Errorf(string, ...interface{})
}

Logger is the interface expected by Finisher.Log. It allows using any logger which implements the Infof() and Errorf() methods.

type Option

type Option func(keeper *serverKeeper) error

Option is what a functional option returns. The function which is returned applies the actual values when invoked later by the Option recipient.

func WithName

func WithName(name string) Option

WithName sets a custom name for the server to register.

The default name is "server" if there will be only one server registered, otherwise the names default to "server #<num>".

func WithTimeout

func WithTimeout(timeout time.Duration) Option

WithTimeout overrides the global Finisher.Timeout for this specific server.

type Server

type Server interface {
	Shutdown(ctx context.Context) error
}

A Server is a type which can be shutdown.

This is the interface expected by Add() which allows registering any server which implements the Shutdown() method.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL