close
Skip to content

[tcp-over-fetch] Buffer request body for non-HTTPS fetches#3356

Merged
adamziel merged 7 commits intotrunkfrom
fix/tcp-over-fetch-http-body-buffering
Mar 8, 2026
Merged

[tcp-over-fetch] Buffer request body for non-HTTPS fetches#3356
adamziel merged 7 commits intotrunkfrom
fix/tcp-over-fetch-http-body-buffering

Conversation

@adamziel
Copy link
Collaborator

@adamziel adamziel commented Mar 8, 2026

Summary

HTTPS requests from PHP (e.g. curl to https://example.com) go through the TLS code path in tcp-over-fetch, which decrypts the traffic and runs a fetch(). When the direct fetch fails due to CORS, fetchWithCorsProxy retries through the CORS proxy. On the local dev server, that proxy is http://localhost — and Chrome refuses to stream request bodies over HTTP/1.1 (ERR_ALPN_NEGOTIATION_FAILED).

The body buffering logic was previously scattered in tcp-over-fetch-websocket.ts and only covered the http:// protocol path, missing the TLS-via-HTTP-CORS-proxy case entirely. This PR moves all buffering decisions into a single httpSafeFetch() wrapper inside fetchWithCorsProxy. Every fetch() call in that function now goes through the wrapper, which buffers the request body whenever the target URL is http://. This handles all cases uniformly: direct requests, localhost, same-origin playground URLs, and CORS proxy fallbacks.

As a bonus, the duplicated request-parsing logic between fetchOverTLS and fetchOverHTTP is consolidated into a shared parseRequestAndFetch helper.

Test plan

  • Existing tests pass (npx nx test php-wasm-web --testFile=tcp-over-fetch-websocket.spec.ts and npx nx test php-wasm-web-service-worker --testFile=fetch-with-cors-proxy.spec.ts)
  • Run local dev server (npm run dev), use a Blueprint that does a curl HTTPS request (e.g. installing a plugin from wordpress.org), confirm it succeeds
  • Verify CURLFile uploads still work on the local dev server

…rrors

HTTPS requests from PHP that fail direct fetch (CORS) and fall back to
a CORS proxy on the local dev server (http://) were failing because
Chrome requires HTTP/2 for streaming request bodies (duplex: 'half')
but the dev server only speaks HTTP/1.1.

Instead of scattering buffering decisions across tcp-over-fetch and
parseHttpRequest, this introduces httpSafeFetch() – a fetch() wrapper
in fetchWithCorsProxy that buffers the request body whenever the target
URL is http://. Every fetch() call in fetchWithCorsProxy now goes
through this wrapper, handling all cases uniformly: direct requests,
localhost, same-origin, and CORS proxy fallbacks.

As a side effect, the duplicated request-parsing logic between
fetchOverTLS and fetchOverHTTP is consolidated into a shared
parseRequestAndFetch helper.
Copilot AI review requested due to automatic review settings March 8, 2026 10:53
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR centralizes request-body buffering to avoid Chrome HTTP/1.1 streaming-body failures (e.g. ERR_ALPN_NEGOTIATION_FAILED) by routing all fetch() calls in the CORS-proxy path through a single wrapper, and consolidates duplicated request parsing/fetch piping logic.

Changes:

  • Introduces httpSafeFetch() in fetchWithCorsProxy() to buffer bodies for http:// targets before calling fetch().
  • Refactors tcp-over-fetch websocket code to use a shared parseRequestAndFetch() helper (including Expect: 100-continue handling).
  • Removes the per-call-site “needsBodyBuffering” logic from RawBytesFetch.parseHttpRequest() and its callers.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
packages/php-wasm/web/src/lib/tcp-over-fetch-websocket.ts Consolidates request parsing + 100-continue handshake + response piping into parseRequestAndFetch() and removes local body-buffering logic.
packages/php-wasm/web-service-worker/src/fetch-with-cors-proxy.ts Adds httpSafeFetch() wrapper and routes all fetch attempts (direct, same-origin, localhost, and proxy fallback) through it to buffer HTTP bodies.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

adamziel and others added 3 commits March 8, 2026 14:51
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Cover the three key behaviors: HTTP requests get their streaming body
buffered before fetch(), HTTPS requests pass through without buffering,
and the CORS proxy retry path correctly buffers and preserves the body
when routed through an http:// proxy (including when init is forwarded).
@adamziel adamziel added [Type] Bug An existing feature does not function as intended [Aspect] Networking labels Mar 8, 2026
adamziel and others added 3 commits March 8, 2026 15:48
The previous tests only checked that the body content survived the
round-trip. They didn't verify that buffering (via Response.arrayBuffer())
actually happened for http:// URLs or that it was correctly skipped for
https:// URLs. Spy on Response.prototype.arrayBuffer to prove the
buffering contract.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the Response.prototype.arrayBuffer spy (which only proved an
implementation detail) with a Proxy on the Request constructor that
records whether each Request was built with a ReadableStream body or
a buffer body. This directly asserts the property that matters: the
body type that reaches new Request() determines whether duplex: 'half'
is needed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Drop the Proxy-on-Request-constructor infrastructure. Instead, check
bodyUsed on the original request (true = body was consumed for
buffering) and identity on the sent request (same object = no
transformation). Much simpler, same coverage.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@adamziel adamziel changed the title [tcp-over-fetch] Buffer request body for HTTP fetches [tcp-over-fetch] Buffer request body for non-HTTPS fetches Mar 8, 2026
@adamziel adamziel merged commit fd61f11 into trunk Mar 8, 2026
42 checks passed
@adamziel adamziel deleted the fix/tcp-over-fetch-http-body-buffering branch March 8, 2026 16:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Aspect] Networking [Type] Bug An existing feature does not function as intended

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants