User Story
As the relay operator, I want connections that stall during the WebSocket upgrade to be dropped within roughly 5 seconds, so that a slow-loris peer cannot pin sockets and goroutines on an internet-exposed surface indefinitely.
Context
http.Server's ReadTimeout/WriteTimeout (set in #9) govern the HTTP request lifecycle but stop applying once the connection is hijacked inside websocket.Accept. Today the three http.Server instances in cmd/pyrycode-relay/main.go set ReadHeaderTimeout: 10 * time.Second; that is the only bound on a peer that opens TCP and then dribbles the WS-upgrade request headers byte-by-byte. The relay is internet-exposed and assumes adversarial input by default (see docs/threat-model.md). Per-frame deadlines (#15) and the heartbeat ping/pong (#7) already cover slow peers after the upgrade succeeds; this ticket targets the pre-upgrade window only.
Acceptance Criteria
Technical Notes
Size Estimate
XS — small set of timeout literals at the wiring site plus a focused slow-handshake test.
User Story
As the relay operator, I want connections that stall during the WebSocket upgrade to be dropped within roughly 5 seconds, so that a slow-loris peer cannot pin sockets and goroutines on an internet-exposed surface indefinitely.
Context
http.Server'sReadTimeout/WriteTimeout(set in #9) govern the HTTP request lifecycle but stop applying once the connection is hijacked insidewebsocket.Accept. Today the threehttp.Serverinstances incmd/pyrycode-relay/main.gosetReadHeaderTimeout: 10 * time.Second; that is the only bound on a peer that opens TCP and then dribbles the WS-upgrade request headers byte-by-byte. The relay is internet-exposed and assumes adversarial input by default (seedocs/threat-model.md). Per-frame deadlines (#15) and the heartbeat ping/pong (#7) already cover slow peers after the upgrade succeeds; this ticket targets the pre-upgrade window only.Acceptance Criteria
/v1/serverand/v1/client.--insecure-listenand--domain(autocert) modes — i.e. across allhttp.Serverinstances configured incmd/pyrycode-relay/main.go.internal/relaypackage simulates a slow upgrade (e.g. anet.Connthat writes upgrade-request bytes with deliberate pauses, or never completes) and asserts the relay closes the connection on or before the configured deadline.docs/PROJECT-MEMORY.md).make vet,make test(-race), andmake buildremain clean.Technical Notes
nhooyr.io/websocket.Acceptruns inside the request context and writes its 101 response viahttp.ResponseWriter. The pre-upgrade portion is governed byhttp.Server.ReadHeaderTimeout; the architect decides whether tightening that field alone meets the bound, or whether a request-scoped guard around the upgrade endpoints is also required./v1/serverand/v1/client; if it lives at thehttp.Serverlevel it implicitly also affects/healthz, which is fine (a healthcheck that takes >5s to send its request is broken).http.Servertimeout values.Size Estimate
XS — small set of timeout literals at the wiring site plus a focused slow-handshake test.