A client that stops sending after the app answers is timed out #135

Merged
clawbot merged 1 commits from issue-130-client-timeout-after-early-answer into next 2026-10-20 11:47:15 +02:00
Collaborator

Fixes #130.

When the app answered before the client had sent its whole body, and its answer ended with its last bytes, responseReceived stopped every timeout, SWWAF_CLIENT_REQUEST_TIMEOUT included. The transport was still waiting to read the client's body, and Go's server takes the body's lock before it sends an answer's headers, so the answer never went out and the request held its connection until the client closed it.

responseReceived now leaves the timeouts running while the client's body has not arrived whole. They end as they do before the app answers: when the client has sent its body, when the app has been sent the whole request, or when the request is handled. When SWWAF_CLIENT_REQUEST_TIMEOUT runs out, requestTimedOut ends the read as it already did for an app whose answer comes in more than one piece.

What the diff does not show:

  • The client is sent the app's answer, such as the 404, once the time runs out, not the 408 of smallwebwaf, since the app's answer had already started; then the connection is closed. The log line has the app's status and the action timed_out.
  • Judgement call: SWWAF_UPSTREAM_REQUEST_TIMEOUT runs on too, as it does before the app answers, so it still ends such a request while SWWAF_CLIENT_REQUEST_TIMEOUT is off.
  • Not fixed here: a client that sends more once the app has answered, then stops with less than 256 KiB of its body left, is still never timed out, filed as #136.

Model: opus-5-5

Fixes https://git.eeqj.de/sneak/smallwebwaf/issues/130. When the app answered before the client had sent its whole body, and its answer ended with its last bytes, `responseReceived` stopped every timeout, `SWWAF_CLIENT_REQUEST_TIMEOUT` included. The transport was still waiting to read the client's body, and Go's server takes the body's lock before it sends an answer's headers, so the answer never went out and the request held its connection until the client closed it. `responseReceived` now leaves the timeouts running while the client's body has not arrived whole. They end as they do before the app answers: when the client has sent its body, when the app has been sent the whole request, or when the request is handled. When `SWWAF_CLIENT_REQUEST_TIMEOUT` runs out, `requestTimedOut` ends the read as it already did for an app whose answer comes in more than one piece. What the diff does not show: - The client is sent the app's answer, such as the 404, once the time runs out, not the 408 of `smallwebwaf`, since the app's answer had already started; then the connection is closed. The log line has the app's status and the action `timed_out`. - Judgement call: `SWWAF_UPSTREAM_REQUEST_TIMEOUT` runs on too, as it does before the app answers, so it still ends such a request while `SWWAF_CLIENT_REQUEST_TIMEOUT` is `off`. - Not fixed here: a client that sends more once the app has answered, then stops with less than 256 KiB of its body left, is still never timed out, filed as https://git.eeqj.de/sneak/smallwebwaf/issues/136. Model: opus-5-5
clawbot added the needs-review label 2026-10-14 19:34:05 +02:00
clawbot self-assigned this 2026-10-14 19:34:05 +02:00
clawbot force-pushed issue-130-client-timeout-after-early-answer from 021e5c4325 to bf73a14b63 2026-10-20 10:40:30 +02:00 Compare
Author
Collaborator

FAIL

  • internal/proxy/timeouts_test.go, TestClientStopsSendingAfterTheAppHasAnswered: the test can fail on a loaded host although smallwebwaf does what SPEC.md asks. If the app's 404 reaches smallwebwaf before it has started waiting for the rest of the client's body, nothing holds the answer up: the client gets the 404 at once, the log line says forward, and the test fails on the time, the action and the metric. Nothing in the test makes the app answer only after that wait has begun, so the order is left to chance. Acceptable: the test makes that order certain, for example by having the app read the bytes the client sent and answer only after a margin that no hold-up the suite allows can close, and where a hold-up can still change what happens, it expects what SPEC.md asks for what did happen, as TestRequestTimeouts does. It must still fail on the code before this change.

Judgement call: the app's answer counts as started once smallwebwaf has taken it, so sending that answer whole and then closing the connection, as the PR body says, is read as what SPEC.md and README.md ask.

Model: opus-5-5

FAIL - `internal/proxy/timeouts_test.go`, `TestClientStopsSendingAfterTheAppHasAnswered`: the test can fail on a loaded host although `smallwebwaf` does what `SPEC.md` asks. If the app's `404` reaches `smallwebwaf` before it has started waiting for the rest of the client's body, nothing holds the answer up: the client gets the `404` at once, the log line says `forward`, and the test fails on the time, the action and the metric. Nothing in the test makes the app answer only after that wait has begun, so the order is left to chance. Acceptable: the test makes that order certain, for example by having the app read the bytes the client sent and answer only after a margin that no hold-up the suite allows can close, and where a hold-up can still change what happens, it expects what `SPEC.md` asks for what did happen, as `TestRequestTimeouts` does. It must still fail on the code before this change. Judgement call: the app's answer counts as started once `smallwebwaf` has taken it, so sending that answer whole and then closing the connection, as the PR body says, is read as what `SPEC.md` and `README.md` ask. Model: opus-5-5
clawbot added needs-rework and removed needs-review labels 2026-10-20 10:57:31 +02:00
clawbot added 1 commit 2026-10-20 11:23:44 +02:00
When the app answered before the client had sent its whole body, and its
answer ended with its last bytes, every timeout was stopped,
SWWAF_CLIENT_REQUEST_TIMEOUT included. The transport went on waiting to
read the body, and Go's server cannot send the answer until that read
returns, so a client that stopped sending held the request until it
closed the connection. The timeouts now run on until the client has sent
its body, as they do before the app answers: once
SWWAF_CLIENT_REQUEST_TIMEOUT runs out the client is sent the app's
answer, the connection is closed, and the log line says timed_out.

Model: opus-5-5
clawbot force-pushed issue-130-client-timeout-after-early-answer from bf73a14b63 to af901ba929 2026-10-20 11:23:44 +02:00 Compare
Author
Collaborator

TestClientStopsSendingAfterTheAppHasAnswered no longer leaves the order to chance: the app takes the bytes the client sent and waits half the timeout before it answers, so smallwebwaf is by then waiting for the rest of the body.
Where a hold-up of the test process still makes the app answer after the time ran out, the test expects what SPEC.md asks for then, as TestRequestTimeouts does: 408, or 504 if the bytes the client sent never reached the app.

Model: opus-5-5

`TestClientStopsSendingAfterTheAppHasAnswered` no longer leaves the order to chance: the app takes the bytes the client sent and waits half the timeout before it answers, so `smallwebwaf` is by then waiting for the rest of the body. Where a hold-up of the test process still makes the app answer after the time ran out, the test expects what `SPEC.md` asks for then, as `TestRequestTimeouts` does: `408`, or `504` if the bytes the client sent never reached the app. Model: opus-5-5
clawbot added needs-review and removed needs-rework labels 2026-10-20 11:23:54 +02:00
Author
Collaborator

PASS

Model: opus-5-5

PASS Model: opus-5-5
clawbot merged commit 6327f7f9c5 into next 2026-10-20 11:47:15 +02:00
clawbot deleted branch issue-130-client-timeout-after-early-answer 2026-10-20 11:47:16 +02:00
Sign in to join this conversation.