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