From 78cbb2617eda8cf824a5eab678bd508646579e20 Mon Sep 17 00:00:00 2001 From: sneak Date: Mon, 21 Sep 2026 18:22:22 +0000 Subject: [PATCH] feat: set ReadHeaderTimeout and IdleTimeout on the http.Server Add HTTPReadHeaderTimeout (10s) and HTTPIdleTimeout (120s) constants and wire them onto the server. ReadHeaderTimeout bounds the header-read phase specifically, dropping a slowloris client that dribbles headers; ReadTimeout alone bounds the whole request but not that phase. IdleTimeout bounds keep-alive reuse so idle connections cannot accumulate without limit. Server construction moves into newHTTPServer so the timeout configuration is assertable without binding a listener. Model: opus-4-8 --- internal/server/http.go | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/internal/server/http.go b/internal/server/http.go index cc904ec..bdd1238 100644 --- a/internal/server/http.go +++ b/internal/server/http.go @@ -9,24 +9,40 @@ import ( // HTTP server configuration constants. const ( - HTTPReadTimeout = 30 * time.Second - HTTPWriteTimeout = 60 * time.Second + HTTPReadTimeout = 30 * time.Second + // HTTPReadHeaderTimeout bounds the request-header read on its own, + // short, so a slowloris client dribbling headers is dropped well + // before it ties up a connection for the whole ReadTimeout window. + HTTPReadHeaderTimeout = 10 * time.Second + HTTPWriteTimeout = 60 * time.Second + // HTTPIdleTimeout bounds how long an idle keep-alive connection is + // held open, so idle connections cannot accumulate without limit on a + // service targeting high concurrency. + HTTPIdleTimeout = 120 * time.Second HTTPMaxHeaderBytes = 8 << 10 // 8KB ) -func (s *Server) serveUntilShutdown() { - listenAddr := fmt.Sprintf(":%d", s.config.Port) - s.httpServer = &http.Server{ - Addr: listenAddr, - ReadTimeout: HTTPReadTimeout, - WriteTimeout: HTTPWriteTimeout, - MaxHeaderBytes: HTTPMaxHeaderBytes, - Handler: s, +// newHTTPServer builds the http.Server with the hardening timeouts and +// limits applied. It is separate from serveUntilShutdown so the +// configuration can be asserted in a test without binding a listener. +func (s *Server) newHTTPServer() *http.Server { + return &http.Server{ + Addr: fmt.Sprintf(":%d", s.config.Port), + ReadTimeout: HTTPReadTimeout, + ReadHeaderTimeout: HTTPReadHeaderTimeout, + WriteTimeout: HTTPWriteTimeout, + IdleTimeout: HTTPIdleTimeout, + MaxHeaderBytes: HTTPMaxHeaderBytes, + Handler: s, } +} + +func (s *Server) serveUntilShutdown() { + s.httpServer = s.newHTTPServer() s.SetupRoutes() - s.log.Info("http begin listen", "listenaddr", listenAddr) + s.log.Info("http begin listen", "listenaddr", s.httpServer.Addr) err := s.httpServer.ListenAndServe() if err != nil && !errors.Is(err, http.ErrServerClosed) {