summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-04-14 10:35:17 +0300
committerPaul Buetow <paul@buetow.org>2026-04-14 10:35:17 +0300
commit28b49a9e8bd0190679b8f60019676a1dab2bcfcc (patch)
tree16dd065cd1d72b418ed9190abf9778696ff636c8 /internal
parent79384e6439c2faf26d68d286a3e814da39e127eb (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.go27
-rw-r--r--internal/daemon/daemon_test.go16
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())