server: set all four http.Server socket timeouts (#118)
check / check (push) Successful in 3m50s
check / check (push) Successful in 3m50s
The http.Server was built with only ReadHeaderTimeout set; the other three timeouts were zero, which in net/http means no limit, so a peer could hold a connection open past the header phase, responses had no write deadline, and keep-alive connections were never reaped. ReadTimeout 15s, WriteTimeout 75s, and IdleTimeout 120s now join ReadHeaderTimeout 10s as named constants. WriteTimeout must stay above the 60s chimw.Timeout handler budget, because net/http arms the write deadline once request headers are read; a test fails the build if either number moves alone. The server literal moved into newHTTPServer so the configuration can be asserted without binding a socket (closes #99) Model: opus-5
This commit was merged in pull request #118.
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
package server_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"sneak.berlin/go/dnswatcher/internal/server"
|
||||
)
|
||||
|
||||
// noopHandler stands in for the router; newHTTPServer only stores it.
|
||||
func noopHandler() http.Handler {
|
||||
return http.HandlerFunc(
|
||||
func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// TestHTTPServerTimeoutsAreSet asserts that every socket-level
|
||||
// timeout is configured. A zero value in net/http means "no limit",
|
||||
// so a refactor that silently drops one of these reintroduces the
|
||||
// slowloris / unreaped-keep-alive exposure this guards against.
|
||||
//
|
||||
// The assertions are on the configured field values only; nothing
|
||||
// here measures elapsed time, so the test cannot flake on timing.
|
||||
func TestHTTPServerTimeoutsAreSet(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
srv := server.NewHTTPServer(":8080", noopHandler())
|
||||
|
||||
if srv.ReadTimeout <= 0 {
|
||||
t.Errorf(
|
||||
"ReadTimeout must be non-zero, got %v",
|
||||
srv.ReadTimeout,
|
||||
)
|
||||
}
|
||||
|
||||
if srv.ReadHeaderTimeout <= 0 {
|
||||
t.Errorf(
|
||||
"ReadHeaderTimeout must be non-zero, got %v",
|
||||
srv.ReadHeaderTimeout,
|
||||
)
|
||||
}
|
||||
|
||||
if srv.WriteTimeout <= 0 {
|
||||
t.Errorf(
|
||||
"WriteTimeout must be non-zero, got %v",
|
||||
srv.WriteTimeout,
|
||||
)
|
||||
}
|
||||
|
||||
if srv.IdleTimeout <= 0 {
|
||||
t.Errorf(
|
||||
"IdleTimeout must be non-zero, got %v",
|
||||
srv.IdleTimeout,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// TestWriteTimeoutExceedsHandlerBudget pins the one relationship the
|
||||
// values must satisfy. net/http arms the write deadline once request
|
||||
// headers are read, so it covers handler execution plus the response
|
||||
// flush. If WriteTimeout were not greater than the chimw.Timeout
|
||||
// handler budget, the connection would be severed before a handler
|
||||
// that used its full budget could respond, making that budget
|
||||
// unreachable.
|
||||
func TestWriteTimeoutExceedsHandlerBudget(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
srv := server.NewHTTPServer(":8080", noopHandler())
|
||||
|
||||
if srv.WriteTimeout <= server.RequestTimeout {
|
||||
t.Errorf(
|
||||
"WriteTimeout (%v) must exceed handler budget (%v)",
|
||||
srv.WriteTimeout,
|
||||
server.RequestTimeout,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// TestReadTimeoutCoversHeaderTimeout asserts the read deadline for
|
||||
// the whole request is at least as long as the header-only deadline;
|
||||
// a smaller ReadTimeout would make ReadHeaderTimeout unreachable.
|
||||
func TestReadTimeoutCoversHeaderTimeout(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
srv := server.NewHTTPServer(":8080", noopHandler())
|
||||
|
||||
if srv.ReadTimeout < srv.ReadHeaderTimeout {
|
||||
t.Errorf(
|
||||
"ReadTimeout (%v) must be >= ReadHeaderTimeout (%v)",
|
||||
srv.ReadTimeout,
|
||||
srv.ReadHeaderTimeout,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// TestHTTPServerAddrAndHandler covers the rest of the constructor so
|
||||
// a future edit cannot drop the listen address or the handler.
|
||||
func TestHTTPServerAddrAndHandler(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
srv := server.NewHTTPServer(":9999", noopHandler())
|
||||
|
||||
if srv.Addr != ":9999" {
|
||||
t.Errorf("Addr = %q, want %q", srv.Addr, ":9999")
|
||||
}
|
||||
|
||||
if srv.Handler == nil {
|
||||
t.Error("Handler must not be nil")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user