Upload Redirect 307 - #245
Conversation
Ookla is migrating its fleet to HTTPS. A migrated server answers a plain-HTTP upload POST with a 307 to its HTTPS endpoint, sending that redirect after ~64 KB and closing the request stream. Since 0.14.0 (#69), the upload body has been streamed in 8 KB chunks rather than handed over as one buffered ByteArrayContent. A streaming body cannot survive that mid-write teardown: it dies with "Unable to write data to the transport connection", so .NET never reads the 307 and never follows it. Every upload request failed, reporting 0 bps. Downloads were unaffected throughout - a GET carries no body, so it replays across the redirect for free. That asymmetry is what made the bug look server-specific rather than transport-shaped. Resolve the upload endpoint once, up front, with an empty-bodied POST: a redirect is decided by scheme and host rather than payload, so it draws the same answer with nothing to write and nothing to tear down. Sending no bytes also keeps the probe out of the measurement, and the per-chunk streaming that #69 introduced for its memory fix is retained. Verified against speedtest-bracknell.vodafone.co.uk (a migrated server): 0 bps / 30 of 30 requests failed, now 176.44 Mbps. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
@claude Review this pull request. Analyse the code changes and provide feedback covering:
|
|
Claude finished @FrankRay78's task in 5m 7s —— View job Review of PR #245 — Upload Redirect 307
Note: CorrectnessThe core fix is sound. One correctness nit: Style / convention (CLAUDE.md,
|
Move ResolveUploadUrlAsync out of #region Static Functions - it is an instance method and was the only one among five static helpers, in a region whose name advertises the opposite. Cover the best-effort fallback. It is a contract stated in both the XML docs and the PR body with nothing exercising it, and the failure mode if it regresses is this bug's own history: a silent 0 bps. The test was confirmed to fail when the fallback is removed, so it is not vacuous. Only the configured URL is mocked, so a resolution yielding anything else leaves the uploads unanswered. Document why IsRedirect treats permanent and temporary redirects alike: an HTTPS migration is as likely to announce itself with a 301 as with the 307 that prompted this, and resolving an address does not carry the method-rewriting semantics that replaying a request would. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
@claude Review the newly pushed commit d0a2b30, which addresses your earlier review. Please check:
Two of your suggestions were deliberately not actioned, and I'd welcome a challenge if you disagree:
Note: the full suite was run locally — 648 pass, 0 fail, 0 skipped, 0 warnings. The fallback test was also confirmed to fail when the fallback is removed. |
|
Claude finished @FrankRay78's task in 1m 20s —— View job Review of commit d0a2b30
Note:
|
Why
Ookla is migrating its speed test fleet to HTTPS. A migrated server answers a plain-HTTP upload POST with a
307to its HTTPS endpoint — and it sends that redirect after roughly 64 KB, closing the request stream while the body is still being written.Since 0.14.0 (#69) the upload body has been streamed in 8 KB chunks instead of handed over as one buffered
ByteArrayContent. A streaming body cannot survive that mid-write teardown: it fails withIOException: Unable to write data to the transport connection, so .NET never reads the307and never follows it. Every upload request failed and the run reported0 bps.Roughly ten months of releases (0.14.0 → 0.24.0) have reported
0 bpsagainst any migrated server — silently, until #222 started surfacing the failure count.Downloads were never affected, which is what made this look server-specific rather than transport-shaped: a GET carries no body, so it replays across the redirect for free. The same server would report 291 Mbps down and 0 bps up.
What changes
Non-obvious things a reviewer should know
GetUploadSpeedAsync_ShouldReturnSpeedTestResult_WhenSuccessful, which tallies every byte the server receives against reported throughput. That test passes unmodified here, and is worth keeping that way.MockHttpMessageHandlerreplaces the transport, so no test can reproduce the actual socket teardown mid-body-write. The test covers the redirect contract; the teardown itself was verified manually against a live migrated server.How to verify
dotnet build src— clean, zero warningsdotnet test src— 647 pass, 0 skippedOoklaSpeedtest.cschange and rundotnet test src --filter "FullyQualifiedName~WhenServerRedirectsUploadEndpoint"0 bps:netpace --no-download --no-latency --server "http://speedtest-bracknell.vodafone.co.uk:8080/speedtest/upload.php"(measured 176.44 Mbps here, against 0 bps / 30 of 30 failed before)
Related
Regression introduced by #69. Failure counts that made it visible came from #222.
Three follow-ups found while diagnosing this, deliberately left out of this branch:
SpeedTestResult, a publicNetPace.CoreAPI change.--no-latencyselectsservers.First()unvalidated. It is what made this bug reproducible, and the server it lands on shifts between releases.