package http import ( "context" "errors" "fmt" "net/http" "time" ) type Timeouts struct { ReadHeader time.Duration Read time.Duration Write time.Duration Idle time.Duration MaxHeaderBytes int } var DefaultTimeouts = Timeouts{ ReadHeader: 10 * time.Second, Read: 30 * time.Second, Write: 60 * time.Second, Idle: 120 * time.Second, MaxHeaderBytes: 64 << 10, } const DefaultShutdownDeadline = 10 * time.Second func NewServer(h http.Handler, t Timeouts) *http.Server { return &http.Server{ Handler: h, ReadHeaderTimeout: t.ReadHeader, ReadTimeout: t.Read, WriteTimeout: t.Write, IdleTimeout: t.Idle, MaxHeaderBytes: t.MaxHeaderBytes, DisableGeneralOptionsHandler: true, } } func Serve(ctx context.Context, records []Listener, t Timeouts, deadline time.Duration, stopping func()) error { servers := make([]*http.Server, len(records)) results := make(chan error, len(records)) for i, r := range records { srv := NewServer(r.Handler, t) servers[i] = srv go func(name string, srv *http.Server, r Listener) { err := srv.Serve(r.Listener) if errors.Is(err, http.ErrServerClosed) { results <- nil return } results <- fmt.Errorf("serve %s listener: %w", name, err) }(r.Name, srv, r) } var serveErr error pending := len(records) select { case <-ctx.Done(): case serveErr = <-results: pending-- } if stopping != nil { stopping() } shutdownCtx, cancel := context.WithTimeout(context.Background(), deadline) defer cancel() var shutdownErr error for i, srv := range servers { if err := srv.Shutdown(shutdownCtx); err != nil { shutdownErr = errors.Join(shutdownErr, fmt.Errorf("shut down %s listener: %w", records[i].Name, err)) } } for ; pending > 0; pending-- { if err := <-results; err != nil && serveErr == nil { serveErr = err } } return errors.Join(serveErr, shutdownErr) }