finish

package module
v1.2.0 Latest Latest
Warning

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

Go to latest
Published: Aug 19, 2022 License: BSD-3-Clause Imports: 8 Imported by: 9

README

finish

Go Reference 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, 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() {
		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(format string, v ...interface{})
	Errorf(format string, v ...interface{})
}

Logger is the interface expected by Finisher.Log.

It allows using any loggers which implement the Infof() and Errorf() methods.

type Option

type Option option

An Option can be used to change the behavior when registering a server via Finisher.Add.

func WithName

func WithName(name string) Option

WithName sets a custom name for the server to be registered via Finisher.Add.

If there will be only one server registered, the name defaults to “server”. Otherwise, the names of the servers default to “server #<num>”.

func WithTimeout

func WithTimeout(timeout time.Duration) Option

WithTimeout overrides the global Finisher.Timeout for the server to be registered via Finisher.Add.

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 Finisher.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