Harden http.Server: slowloris timeouts and form body limit (closes #92)
check / check (push) Failing after 1s
check / check (push) Failing after 1s
The http.Server now sets ReadHeaderTimeout (10s), which bounds the slow header dribble that ReadTimeout alone does not, and IdleTimeout (120s), which bounds keep-alive reuse. Server construction moved into a small helper so a test can assert the timeouts without binding a listener. POST / and POST /generate bodies are capped at 1 MiB and an oversized body returns 413. What a reader would trip over: the CSRF library reads its token from the form and swallows a parse error, so a cap applied only inside it would surface as 403. The body limit therefore parses the form under the cap before the CSRF check; the parsed form is reused afterwards. A test covers an oversized body that carries a valid token. Judgement call: WriteTimeout stays at 60s; it also bounds how long a large image may take to send over a slow link. Model: opus-4-8 (implementation, review); fable-5-1 (landing message)
This commit was merged in pull request #123.
This commit is contained in:
+27
-11
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user