diff options
author | Marc Pervaz Boocha <mboocha@sudomsg.com> | 2025-08-02 20:55:11 +0530 |
---|---|---|
committer | Marc Pervaz Boocha <mboocha@sudomsg.com> | 2025-08-02 20:55:11 +0530 |
commit | ce6cf13c2d67c3368251d1eea5593198f5021330 (patch) | |
tree | d4c2347fd45fce395bf22a30e423697494d4284f /http/server.go | |
download | kit-0.1.0.tar kit-0.1.0.tar.gz kit-0.1.0.tar.bz2 kit-0.1.0.tar.lz kit-0.1.0.tar.xz kit-0.1.0.tar.zst kit-0.1.0.zip |
Initial Commitv0.1.0
Diffstat (limited to '')
-rw-r--r-- | http/server.go | 146 |
1 files changed, 146 insertions, 0 deletions
diff --git a/http/server.go b/http/server.go new file mode 100644 index 0000000..e13bc67 --- /dev/null +++ b/http/server.go @@ -0,0 +1,146 @@ +// Package http provides utilities for managing HTTP servers and listeners. +// +// It supports opening multiple network listeners from configuration, +// running multiple HTTP servers concurrently with graceful shutdown, +// and integrates structured logging with context. +// +// This package is designed to be used with context cancellation for concurrency control. + +package http + +import ( + "context" + "errors" + "fmt" + "log/slog" + "net" + "net/http" + "sync" + "time" + + "go.sudomsg.com/kit/logging" + "golang.org/x/sync/errgroup" +) + +// Listeners is a slice of net.Listener interfaces representing multiple network listeners. +type Listeners []net.Listener + +// CloseAll closes all listeners in the slice. +// It aggregates all errors returned by individual Close calls using errors.Join. +// +// If no errors occur, it returns nil. +func (ls Listeners) CloseAll() error { + var errs []error + for _, l := range ls { + if err := l.Close(); err != nil { + errs = append(errs, err) + } + } + if len(errs) > 0 { + return errors.Join(errs...) + } + return nil +} + +// ServerConfig defines a single network listener configuration. +// +// Network is the network type, e.g., "tcp". +// Address is the socket address, e.g., ":8080". +type ServerConfig struct { + Network string `toml:"network"` + Address string `toml:"network"` +} + +// OpenConfigListeners opens network listeners as specified by the provided ServerConfig slice. +// +// It attempts to open all listeners concurrently and returns them if all succeed. +// +// If any listener fails to open, it closes all previously opened listeners and returns an error. +// +// The context controls cancellation of the opening process. +func OpenConfigListners(ctx context.Context, config []ServerConfig) (Listeners, error) { + lns := make(Listeners, 0, len(config)) + var mu sync.Mutex + g, ctx := errgroup.WithContext(ctx) + var lc net.ListenConfig + for _, cfg := range config { + g.Go(func() error { + network := cfg.Network + if network == "" { + network = "tcp" + } + ln, err := lc.Listen(ctx, network, cfg.Address) + if err != nil { + return fmt.Errorf("failed to listen on %s %s: %w", network, cfg.Address, err) + } + + mu.Lock() + lns = append(lns, ln) + mu.Unlock() + return nil + }) + } + if err := g.Wait(); err != nil { + _ = lns.CloseAll() + return nil, err + } + return lns, nil +} + +func OpenListeners(ctx context.Context, config []ServerConfig) (Listeners, error) { + return OpenConfigListners(ctx, config) +} + +// 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 && !errors.Is(err, http.ErrServerClosed) { + 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) + go func() { + <-ctx.Done() + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + err := srv.Shutdown(ctx) + if err != nil { + logger.Log(ctx, slog.LevelWarn, "HTTP server Shutdown Error", slog.Any("error", err)) + } else { + logger.Log(ctx, slog.LevelInfo, "HTTP Server Shutdown Complete") + } + }() + return srv.Serve(ln) +} |