From b774e23ac0946dbd53be77981260a5f0c35b2dde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20O=2E=20Sel=C3=A5sdal?= Date: Sat, 29 Aug 2026 00:45:27 +0200 Subject: [PATCH] Configure HTTP server timeouts and graceful shutdown --- main.go | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index bbd6834..114db42 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "crypto/rand" "encoding/json" "errors" @@ -12,8 +13,10 @@ import ( "mime" "net/http" "os" + "os/signal" "path/filepath" "strings" + "syscall" "time" ) @@ -60,8 +63,38 @@ func main() { log.Print("Cleaning old files is disabled") } - if err := http.ListenAndServe(":"+port, nil); err != nil { + // Note: ReadTimeout/WriteTimeout are intentionally not set since they + // would cap the total duration of large file uploads/downloads. + // ReadHeaderTimeout and IdleTimeout still protect against slow-loris + // style attacks on connection/header handling. + srv := &http.Server{ + Addr: ":" + port, + ReadHeaderTimeout: 10 * time.Second, + IdleTimeout: 120 * time.Second, + } + + serverErr := make(chan error, 1) + go func() { + if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + serverErr <- err + } + close(serverErr) + }() + + ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) + defer stop() + + select { + case err := <-serverErr: log.Fatalf("Server failed: %v", err) + case <-ctx.Done(): + log.Print("Shutdown signal received, shutting down gracefully...") + shutdownCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() + if err := srv.Shutdown(shutdownCtx); err != nil { + log.Fatalf("Graceful shutdown failed: %v", err) + } + log.Print("Server stopped") } }