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
}
|