aboutsummaryrefslogtreecommitdiffstats
path: root/http/http.go
blob: 063c1e8fd32af65e381650711eb3da8b0847f56f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
package http

import (
	"context"
	"fmt"
	"go.sudomsg.com/kit/logging"
	"golang.org/x/sync/errgroup"
	"log/slog"
	"net"
	"net/http"
)

// RunHTTPServers runs HTTP servers concurrently on all provided listeners.
//
// The provided handler is used for all servers.
// Servers respond to context cancellation by performing a graceful shutdown with a timeout of 10 seconds.
//
// Logging is performed using the slog.Logger extracted from context.
// Each server logs startup, shutdown, and errors.
//
// This function blocks until all servers have stopped or an error occurs.
func RunHTTPServers(ctx context.Context, lns Listeners, handler http.Handler) error {
	g, ctx := errgroup.WithContext(ctx)

	for _, ln := range lns {
		g.Go(func() error {
			logger, ctx := logging.With(ctx, "address", ln.Addr())

			srv := &http.Server{
				Addr:    ln.Addr().String(),
				Handler: handler,
				BaseContext: func(l net.Listener) context.Context {
					return ctx
				},
				ErrorLog: slog.NewLogLogger(logger.Handler(), slog.LevelError),
			}

			logger.Log(ctx, slog.LevelInfo, "HTTP server serving")

			if err := httpServeContext(ctx, srv, ln); err != nil {
				return fmt.Errorf("HTTP server Serve Error: %w", err)
			}
			return nil
		})

	}
	return g.Wait()
}

func httpServeContext(ctx context.Context, srv *http.Server, ln net.Listener) error {
	logger := logging.FromContext(ctx)

	shutdownErrCh := make(chan error, 1)

	go func() {
		<-ctx.Done()
		shutdownErrCh <- httpServeShutdown(srv, logger)
	}()
	serveErr := srv.Serve(ln)

	// Always wait for shutdown result
	shutdownErr := <-shutdownErrCh

	// Prioritize Serve error
	if serveErr != nil && !errors.Is(serveErr, http.ErrServerClosed) {
		return fmt.Errorf("http serve error: %w", serveErr)
	}
	if shutdownErr != nil {
		return fmt.Errorf("http shutdown error: %w", shutdownErr)
	}
	return nil
}

func httpServeShutdown(srv *http.Server, logger *slog.Logger) error {
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	if err := srv.Shutdown(ctx); err != nil {
		logger.Log(ctx, slog.LevelWarn, "HTTP server Shutdown Error", slog.Any("error", err))
		return err
	}

	logger.Log(ctx, slog.LevelInfo, "HTTP Server Shutdown Complete")
	return nil
}