rsvp

package module
v1.0.1 Latest Latest
Warning

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

Go to latest
Published: Jun 22, 2026 License: MIT Imports: 10 Imported by: 0

README

rsvp

rsvp is a small Go library that makes it easy to run HTTP servers with graceful shutdown functionality.

The API is just two functions: rsvp.ListenAndServe is a drop-in replacement for the standard library http.ListenAndServe function but adds graceful shutdown. For more advanced use cases, rsvp.Run runs an http.Server with graceful shutdown.

The default behavior can be customized with options to set trigger signals, shutdown timeout and context, and a logging function.

Installation

go get github.com/jbarham/rsvp

Motivation and Usage

Why does graceful shutdown matter? Because by default, Go HTTP servers will terminate in-flight requests when they're stopped by signals like SIGTERM or SIGINT. This can have unwanted consequences in production systems if those terminated requests were midway through operations such as updating a customer account.

The following sample code demonstrates how rsvp works to gracefully shut down Go HTTP servers without terminating in-flight requests:

package main

import (
	"flag"
	"log"
	"net/http"
	"strconv"
	"time"

	"github.com/jbarham/rsvp"
)

var runGracefully = flag.Bool("graceful", false, "set to shut down gracefully")

func main() {
	flag.Parse()

	http.HandleFunc("GET /{delay}", func(w http.ResponseWriter, r *http.Request) {
		delay, err := strconv.Atoi(r.PathValue("delay"))
		if err != nil {
			w.WriteHeader(http.StatusBadRequest)
			return
		}
		log.Printf("Starting request with %ds delay...", delay)
		time.Sleep(time.Duration(delay) * time.Second)
		log.Print("Finished request")
	})

	if *runGracefully {
		rsvp.ListenAndServe(":8080", nil)
	} else {
		http.ListenAndServe(":8080", nil)
	}
}

Run go run github.com/jbarham/rsvp/demo to start the above HTTP server without graceful shutdown. Load http://localhost:8080/5 to trigger a 5 second request, then stop the server with Ctrl-C and note that the in-flight request is terminated.

Run go run github.com/jbarham/rsvp/demo -graceful to enable graceful shutdown, repeat the above steps and note that the in-flight request is allowed to finish before the server shuts down.

Reference

https://pkg.go.dev/github.com/jbarham/rsvp

Documentation

Overview

Package rsvp provides a simple way to run an HTTP server with graceful shutdown functionality.

It listens for specified OS signals (SIGTERM and SIGINT by default) and shuts down the server gracefully, by calling http.Server.Shutdown, when one of those signals is received. In particular, this allows the server to finish handling any in-flight HTTP requests before shutting down, instead of terminating immediately.

The simplest way to use rsvp is to replace calls to http.ListenAndServe with rsvp.ListenAndServe, which has the same signature but adds graceful shutdown functionality.

For more advanced use cases, you can create an http.Server and call rsvp.Run directly.

The default behavior can be customized with the provided Option functions.

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrNilServer = errors.New("rsvp: server is nil")

Functions

func ListenAndServe

func ListenAndServe(addr string, handler http.Handler, opts ...Option) error

ListenAndServe is a convenience wrapper around Run that creates an http.Server with the given address and handler.

ListenAndServe is intended primarily as a drop-in replacement for http.ListenAndServe which adds graceful shutdown functionality.

Example
package main

import (
	"log"
	"net/http"
	"strconv"
	"time"

	"github.com/jbarham/rsvp"
)

func main() {
	http.HandleFunc("GET /{delay}", func(w http.ResponseWriter, r *http.Request) {
		delay, err := strconv.Atoi(r.PathValue("delay"))
		if err != nil {
			w.WriteHeader(http.StatusBadRequest)
			return
		}
		log.Printf("Starting request with %ds delay...", delay)
		time.Sleep(time.Duration(delay) * time.Second)
		log.Print("Finished request")
	})
	rsvp.ListenAndServe(":8080", nil)
}

func Run

func Run(server *http.Server, opts ...Option) error

Run starts the HTTP server and shuts it down gracefully when a signal is received.

Run returns any error that occurs during startup or shutdown. It returns nil on successful shutdown.

Example
package main

import (
	"fmt"
	"log"
	"net/http"
	"sync"

	"github.com/jbarham/rsvp"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "Hello, world!")
	})
	server := &http.Server{Addr: ":8080"}
	// Registered shutdown functions are run in separate goroutines,
	// so we need to wait for them to finish before exiting main.
	var wg sync.WaitGroup
	wg.Add(1)
	server.RegisterOnShutdown(func() {
		log.Print("Server is shutting down, cleaning up...")
		// Close database connections, WebSockets etc
		wg.Done()
	})
	rsvp.Run(server)
	wg.Wait()
	log.Print("Shutdown functions complete, exiting")
}

Types

type Option

type Option func(*runner)

Option functions configure the behavior of the graceful shutdown process.

func WithLogFunc

func WithLogFunc(logger func(msg string)) Option

WithLogFunc sets the logging function that will be used to log messages during the server lifecycle.

The default logging function calls log.Println. Setting the logging function to nil will disable logging.

Example
package main

import (
	"fmt"
	"log/slog"
	"net/http"

	"github.com/jbarham/rsvp"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "Hello, world!")
	})
	logFunc := func(msg string) {
		slog.Info(msg)
	}
	rsvp.ListenAndServe(":8080", nil, rsvp.WithLogFunc(logFunc))
}

func WithShutdownContext

func WithShutdownContext(ctx context.Context) Option

WithShutdownContext sets the context that will be used for the shutdown process. The default context is context.Background.

If a timeout is also set by WithTimeout, the shutdown will be canceled if it takes longer than the timeout, even if the context itself has not been canceled.

func WithSignals

func WithSignals(sig os.Signal, extra ...os.Signal) Option

WithSignals sets the signals that will trigger the shutdown process. The default signals are SIGTERM and SIGINT.

func WithTimeout

func WithTimeout(t time.Duration) Option

WithTimeout sets the timeout for the shutdown process. If the shutdown takes longer than this, shutdown will be canceled and Run will return the context.DeadlineExceeded error.

Without a timeout (the default case), the shutdown process will wait indefinitely for in-flight requests to finish before shutting down the server. In practice, service managers like systemd will send a final non-catchable SIGKILL signal after a certain timeout if the process has not exited.

Example
package main

import (
	"log"
	"net/http"
	"time"

	"github.com/jbarham/rsvp"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		log.Print("A slow request is being processed...")
		time.Sleep(10 * time.Second)
		log.Print("Slow request completed")
	})
	if err := rsvp.ListenAndServe(":8080", nil, rsvp.WithTimeout(5*time.Second)); err != nil {
		log.Fatalf("Shutdown error: %s", err)
	}
}

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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