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
This commit is contained in:
2026-09-21 18:22:22 +00:00
parent 930e4eb026
commit 78cbb2617e
+27 -11
View File
@@ -9,24 +9,40 @@ import (
// HTTP server configuration constants. // HTTP server configuration constants.
const ( const (
HTTPReadTimeout = 30 * time.Second HTTPReadTimeout = 30 * time.Second
HTTPWriteTimeout = 60 * 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 HTTPMaxHeaderBytes = 8 << 10 // 8KB
) )
func (s *Server) serveUntilShutdown() { // newHTTPServer builds the http.Server with the hardening timeouts and
listenAddr := fmt.Sprintf(":%d", s.config.Port) // limits applied. It is separate from serveUntilShutdown so the
s.httpServer = &http.Server{ // configuration can be asserted in a test without binding a listener.
Addr: listenAddr, func (s *Server) newHTTPServer() *http.Server {
ReadTimeout: HTTPReadTimeout, return &http.Server{
WriteTimeout: HTTPWriteTimeout, Addr: fmt.Sprintf(":%d", s.config.Port),
MaxHeaderBytes: HTTPMaxHeaderBytes, ReadTimeout: HTTPReadTimeout,
Handler: s, ReadHeaderTimeout: HTTPReadHeaderTimeout,
WriteTimeout: HTTPWriteTimeout,
IdleTimeout: HTTPIdleTimeout,
MaxHeaderBytes: HTTPMaxHeaderBytes,
Handler: s,
} }
}
func (s *Server) serveUntilShutdown() {
s.httpServer = s.newHTTPServer()
s.SetupRoutes() s.SetupRoutes()
s.log.Info("http begin listen", "listenaddr", listenAddr) s.log.Info("http begin listen", "listenaddr", s.httpServer.Addr)
err := s.httpServer.ListenAndServe() err := s.httpServer.ListenAndServe()
if err != nil && !errors.Is(err, http.ErrServerClosed) { if err != nil && !errors.Is(err, http.ErrServerClosed) {