1package http
2
3import (
4 "context"
5 "errors"
6 "fmt"
7 "net/http"
8 "time"
9)
10
11type Timeouts struct {
12 ReadHeader time.Duration
13 Read time.Duration
14 Write time.Duration
15 Idle time.Duration
16 MaxHeaderBytes int
17}
18
19var DefaultTimeouts = Timeouts{
20 ReadHeader: 10 * time.Second,
21 Read: 30 * time.Second,
22 Write: 60 * time.Second,
23 Idle: 120 * time.Second,
24 MaxHeaderBytes: 64 << 10,
25}
26
27const DefaultShutdownDeadline = 10 * time.Second
28
29func NewServer(h http.Handler, t Timeouts) *http.Server {
30 return &http.Server{
31 Handler: h,
32 ReadHeaderTimeout: t.ReadHeader,
33 ReadTimeout: t.Read,
34 WriteTimeout: t.Write,
35 IdleTimeout: t.Idle,
36 MaxHeaderBytes: t.MaxHeaderBytes,
37 DisableGeneralOptionsHandler: true,
38 }
39}
40
41func Serve(ctx context.Context, records []Listener, t Timeouts, deadline time.Duration, stopping func()) error {
42 servers := make([]*http.Server, len(records))
43 results := make(chan error, len(records))
44 for i, r := range records {
45 srv := NewServer(r.Handler, t)
46 servers[i] = srv
47 go func(name string, srv *http.Server, r Listener) {
48 err := srv.Serve(r.Listener)
49 if errors.Is(err, http.ErrServerClosed) {
50 results <- nil
51 return
52 }
53 results <- fmt.Errorf("serve %s listener: %w", name, err)
54 }(r.Name, srv, r)
55 }
56
57 var serveErr error
58 pending := len(records)
59 select {
60 case <-ctx.Done():
61 case serveErr = <-results:
62 pending--
63 }
64
65 if stopping != nil {
66 stopping()
67 }
68
69 shutdownCtx, cancel := context.WithTimeout(context.Background(), deadline)
70 defer cancel()
71 var shutdownErr error
72 for i, srv := range servers {
73 if err := srv.Shutdown(shutdownCtx); err != nil {
74 shutdownErr = errors.Join(shutdownErr, fmt.Errorf("shut down %s listener: %w", records[i].Name, err))
75 }
76 }
77 for ; pending > 0; pending-- {
78 if err := <-results; err != nil && serveErr == nil {
79 serveErr = err
80 }
81 }
82 return errors.Join(serveErr, shutdownErr)
83}