diff options
| author | Paul Buetow <paul@buetow.org> | 2026-04-14 10:35:17 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-04-14 10:35:17 +0300 |
| commit | 28b49a9e8bd0190679b8f60019676a1dab2bcfcc (patch) | |
| tree | 16dd065cd1d72b418ed9190abf9778696ff636c8 /internal | |
| parent | 79384e6439c2faf26d68d286a3e814da39e127eb (diff) | |
daemon: set http.Server timeouts for Run (k3)
Configure ReadHeaderTimeout, ReadTimeout, WriteTimeout, and IdleTimeout
on the daemon listener to mitigate slowloris and stuck clients.
Made-with: Cursor
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/daemon/daemon.go | 27 | ||||
| -rw-r--r-- | internal/daemon/daemon_test.go | 16 |
2 files changed, 38 insertions, 5 deletions
diff --git a/internal/daemon/daemon.go b/internal/daemon/daemon.go index 893e062..22d9f1a 100644 --- a/internal/daemon/daemon.go +++ b/internal/daemon/daemon.go @@ -5,6 +5,7 @@ import ( "context" "fmt" "io" + "log" "log/slog" "net/http" "os" @@ -15,6 +16,13 @@ import ( "codeberg.org/snonux/goprecords/internal/goprecords" ) +const ( + defaultReadHeaderTimeout = 10 * time.Second + defaultReadTimeout = 2 * time.Minute + defaultWriteTimeout = 2 * time.Minute + defaultIdleTimeout = 2 * time.Minute +) + type Config struct { StatsDir string Addr string @@ -84,6 +92,18 @@ func withAccessLog(log *slog.Logger, next http.Handler) http.Handler { }) } +func newDaemonHTTPServer(addr string, handler http.Handler, errLog *log.Logger) *http.Server { + return &http.Server{ + Addr: addr, + Handler: handler, + ErrorLog: errLog, + ReadHeaderTimeout: defaultReadHeaderTimeout, + ReadTimeout: defaultReadTimeout, + WriteTimeout: defaultWriteTimeout, + IdleTimeout: defaultIdleTimeout, + } +} + func Run(ctx context.Context, cfg Config) error { if cfg.StatsDir == "" { return fmt.Errorf("stats directory is required") @@ -98,11 +118,8 @@ func Run(ctx context.Context, cfg Config) error { return fmt.Errorf("auth db: %w", err) } defer store.Close() - srv := &http.Server{ - Addr: cfg.Addr, - Handler: withAccessLog(log, routes(cfg.StatsDir, cfg.AuthDB, store)), - ErrorLog: slog.NewLogLogger(textHandler, slog.LevelError), - } + srv := newDaemonHTTPServer(cfg.Addr, withAccessLog(log, routes(cfg.StatsDir, cfg.AuthDB, store)), + slog.NewLogLogger(textHandler, slog.LevelError)) log.Info("daemon_listen", "addr", cfg.Addr) errCh := make(chan error, 1) go func() { errCh <- srv.ListenAndServe() }() diff --git a/internal/daemon/daemon_test.go b/internal/daemon/daemon_test.go index 60c6afa..ccc186e 100644 --- a/internal/daemon/daemon_test.go +++ b/internal/daemon/daemon_test.go @@ -316,6 +316,22 @@ func TestRunEmptyAddr(t *testing.T) { } } +func TestNewDaemonHTTPServerTimeouts(t *testing.T) { + s := newDaemonHTTPServer("127.0.0.1:0", http.NotFoundHandler(), nil) + if g, w := s.ReadHeaderTimeout, 10*time.Second; g != w { + t.Fatalf("ReadHeaderTimeout %v want %v", g, w) + } + if g, w := s.ReadTimeout, 2*time.Minute; g != w { + t.Fatalf("ReadTimeout %v want %v", g, w) + } + if g, w := s.WriteTimeout, 2*time.Minute; g != w { + t.Fatalf("WriteTimeout %v want %v", g, w) + } + if g, w := s.IdleTimeout, 2*time.Minute; g != w { + t.Fatalf("IdleTimeout %v want %v", g, w) + } +} + func TestRunWritesDaemonListenToLogOutput(t *testing.T) { var buf syncBuffer ctx, cancel := context.WithCancel(context.Background()) |
