Skip to content

node:http: slice 'upgrade' head to the post-body remainder and close on unsatisfied-body FIN - #35983

Closed
robobun wants to merge 10 commits into
mainfrom
farm/abd852f1/http-upgrade-body-head
Closed

robobun wants to merge 10 commits into
mainfrom
farm/abd852f1/http-upgrade-body-head

Conversation

@robobun

@robobun robobun commented Jul 26, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

const server = http.createServer();
server.on('upgrade', (req, socket, head) => {
  // CL:5 + 'HELLOAFTER' in one packet:
  // Node: head === 'AFTER', req.complete === true, req.readableLength === 5
  // Bun:  head === '',      req.complete === false, 'AFTER' -> socket.on('data')
});

Node emits 'upgrade' after the parser.execute() that consumed the header block returns: any declared body bytes present in that same chunk are already buffered on req (with req.complete === true), and head is the chunk remainder after the body. Bun emitted 'upgrade' before body parsing with an empty head, then routed the same-chunk remainder to the socket's raw-data path.

Separately, a peer FIN with the declared body unsatisfied left the tunnel-after-body socket half-open and the pending-request ref unreleased, so server.close() never completed.

Fix

  • HttpParser.h: for a body-carrying Upgrade, pre-compute req->head as the post-body slice of this chunk (Content-Length arithmetic; for a chunked body, a local-state pre-scan of the same bytes) and set bodyCompleteInHead when the body completes in-chunk.
  • HttpContext.h: set HTTP_NODE_TUNNEL_HEAD_PENDING when bodyCompleteInHead and the dispatcher accepted the upgrade, and skip the one onSocketData call for the same-chunk remainder (cleared at end of each onData, so later packets flow normally). onEnd now closes the connection when the body never completed (Node's UpgradeStream wraps a socket whose socketOnEnd still socket.end()s here).
  • _http_server.ts: pass connectHead through as head (native already sliced it). When bodyCompleteInHead, defer the 'upgrade' emit to the body fin in onDataIncomingMessage so the listener observes req.complete and the buffered body like Node.

Verification

test/js/node/http/node-http-upgrade-with-body.test.ts covers: same-chunk CL / chunked / chunked-with-trailer (head="AFTER"), same-chunk no-remainder (head=""), split body (head="", later bytes -> socket data), later tunnel bytes after a same-chunk head, unsatisfied-body FIN (socket closes, server.close() completes), and a spawned process-exit check. All pass under this change and under Node v26; 8/10 fail or hang on main.

All test/js/node/test/parallel/test-http-upgrade-* and test-http-connect* continue to pass.

Supersedes #35974 (CL-only, JS-side slice) and #35063 (the onEnd close half): this handles chunked bodies and matches Node's emit timing (req.complete / readableLength at 'upgrade' time).


[review] gate passed · iteration 3 · 7 files touched

fails on main (without fix)
ASAN without fix: 5 FAILED
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/node/http/node-http-upgrade-with-body.test.ts
bun test v1.4.0 (57f25c79c)

test/js/node/http/node-http-upgrade-with-body.test.ts:
204 |       );
205 |       await once(server, "upgrade");
206 |       client.write("MORE");
207 |       client.on("end", () => client.end());
208 |       const result = await promise;
209 |       expect(result).toEqual({ head: "AFTER", reqData: "HELLO", sockData: "MORE" });
                           ^
error: expect(received).toEqual(expected)

  {
-   "head": "AFTER",
+   "head": "",
    "reqData": "HELLO",
-   "sockData": "MORE",
+   "sockData": "AFTER",
  }

- Expected  - 2
+ Received  + 2

      at <anonymous> (/workspace/bun/test/js/node/http/node-http-upgrade-with-body.test.ts:209:22)
66 |   // once through req, and does not surface the remainder on socket 'data'.
67 |   test.concurrent("same-chunk Content-Length body: head is the post-body remainder", async () => {
68 |     const result = await observeUpgrade(
69 |       "GET / HTTP/1.1\r\nHost: x\r\nConnection: Upgrade\r\nUpgrade: x\r\nConten
... (truncated)

release without fix: all passed
bun test v1.4.0-canary.1 (57f25c79c)

test/js/node/http/node-http-upgrade-with-body.test.ts:
(pass) node:http 'upgrade' with a request body > peer FIN with unsatisfied body: socket closes and server.close() completes [16.39ms]
(pass) node:http 'upgrade' with a request body > same-chunk Content-Length body: head is the post-body remainder [23.69ms]
(pass) node:http 'upgrade' with a request body > same-chunk body with no remainder: head is empty [20.52ms]
(pass) node:http 'upgrade' with a request body > no body: head is the post-header remainder [17.93ms]
(pass) node:http 'upgrade' with a request body > peer FIN with partial body: socket closes [16.87ms]
(pass) node:http 'upgrade' with a request body > same-chunk chunked body: body reaches req, remainder reaches socket 'data' [21.19ms]
(pass) node:http 'upgrade' with a request body > split header block + large tail: no tunnel bytes are dropped [18.65ms]
(pass) node:http 'upgrade' with a request body > split body: head is empty, later post-body bytes reach socket 'data' [20.37ms]
(pass) node:http 'upgrade' with a request body > later tunnel bytes after a same-chunk head reach socket 'data' [19.74ms]
(pass) process exit
... (truncated)
passes on PR (with fix)
ASAN with fix: all passed
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/node/http/node-http-upgrade-with-body.test.ts
bun test v1.4.0 (57f25c79c)

test/js/node/http/node-http-upgrade-with-body.test.ts:
(pass) node:http 'upgrade' with a request body > same-chunk Content-Length body: head is the post-body remainder [922.56ms]
(pass) node:http 'upgrade' with a request body > same-chunk body with no remainder: head is empty [834.10ms]
(pass) node:http 'upgrade' with a request body > same-chunk chunked body: body reaches req, remainder reaches socket 'data' [877.98ms]
(pass) node:http 'upgrade' with a request body > split body: head is empty, later post-body bytes reach socket 'data' [926.08ms]
(pass) node:http 'upgrade' with a request body > later tunnel bytes after a same-chunk head reach socket 'data' [922.81ms]
(pass) node:http 'upgrade' with a request body > peer FIN with unsatisfied body: socket closes and server.close() completes [291.61ms]
(pass) node:http 'upgrade' with a request body > no body: head is the post-header remainder [353.16ms]
(pass) node:http 'upgrade' with a request body > peer F
... (truncated)

release with fix: all passed
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped) in 704ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[1/26] gen cpp.rs (cppbind)
[2/26] gen generated_host_exports.rs
generated_host_exports.rs: 94 exports (host=3, lazy=10, generic=81, rust=0); 240 extern-C blocks audited
[3/26] gen JS modules (bundle-modules)
Preprocess modules (9154ms)
Bundle modules (47ms)
Postprocesss modules (169ms)
Bundle Functions (724ms)
Generate Code (21ms)

[10.13s] Bundled "src/js" for production
  2571 kb
  193 internal modules
  13 native modules
  90 internal functions across 19 files
[3/14] cargo bun_bin → libbun_rust.a (--target x86_64-unknown-linux-gnu)

  nightly-2026-07-20-x86_64-unknown-linux-gnu unchanged - rustc 1.99.0-nightly (9f36de775 2026-07-19)

�[1m�[92m   Compiling�[0m bun_io v0.0.0 (/workspace/bun/src/io)
�[1m�[92m   Compiling�[0m bun_parsers v0.0.0 (/workspace/bun/src/parsers)
�[1m�[92m   Compiling�[0m bun_react_compiler v0.0.0 (/workspace/bun/src/react_compiler)
�[1m�[92m   Compiling�[0m bun_zlib v0.0.0 (/workspace/bun/src/zlib)
�[1m�[92m   Compiling�[0m bun_event_loop v0.0.0 (/workspace/bun/src/event_loop)
... (truncated)
diff hotspot
packages/bun-uws/src/HttpContext.h                 |  27 +-
 packages/bun-uws/src/HttpParser.h                  |  21 +-
 packages/bun-uws/src/HttpResponseData.h            |   5 +
 src/js/internal/http.ts                            |  12 +
 src/js/node/_http_server.ts                        |  59 ++--
 src/jsc/bindings/NodeHTTP.cpp                      |   1 +
 .../node/http/node-http-upgrade-with-body.test.ts  | 356 +++++++++++++++++++++
 7 files changed, 450 insertions(+), 31 deletions(-)

gate history · 6 passed · 2 rejected · iteration 3

evidence per changed file
file                                                   reads  edits  tests
packages/bun-uws/src/HttpContext.h                        10     12      0
packages/bun-uws/src/HttpParser.h                         11     13      0
packages/bun-uws/src/HttpResponseData.h                    1      1      0
src/js/internal/http.ts                                    6      7      0
src/js/node/_http_server.ts                               10      8      0
src/jsc/bindings/NodeHTTP.cpp                              3      1      0
test/js/node/http/node-http-upgrade-with-body.test.ts      4     18      0

…on unsatisfied-body FIN

Node emits 'upgrade' after the parser.execute() that consumed the header
block returns, with head = the remainder of that same chunk AFTER the
declared body, the body already buffered on req, and req.complete true.
Bun emitted 'upgrade' before the body was parsed with an empty head, then
routed the same-chunk remainder to the socket's raw-data path.

HttpParser now pre-computes head as the post-body slice (for both
Content-Length and a chunked body that completes in-chunk) and reports
bodyCompleteInHead; the dispatcher defers the 'upgrade' emit to the body
fin so the listener observes req.complete. A new state bit suppresses the
same-chunk remainder from onSocketData for that one parse call only.

A peer FIN with the declared body unsatisfied left the tunnel-after-body
socket half-open and never released the pending-request ref; onEnd now
closes it (like Node's UpgradeStream), which drives socket 'close' and
lets server.close() complete.
@coderabbitai

coderabbitai Bot commented Jul 26, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Changes

Node http upgrade handling now separates request bodies from tunnel bytes, reports whether the body completes in the initial parse buffer, defers upgrade emission until body completion when needed, and closes incomplete tunnels. Tests cover packetization, buffering, EOF, and shutdown behavior.

Node HTTP upgrade body handling

Layer / File(s) Summary
Parse bodies and suppress same-chunk tunnel data
packages/bun-uws/src/HttpParser.h, packages/bun-uws/src/HttpResponseData.h, packages/bun-uws/src/HttpContext.h
Upgrade parsing now exposes post-body head data and bodyCompleteInHead; same-chunk tunnel delivery is suppressed once, and incomplete after-body tunnels close on EOF.
Defer upgrade emission until body completion
src/jsc/bindings/NodeHTTP.cpp, src/js/node/_http_server.ts, src/js/internal/http.ts
The native callback passes body completion state, and JavaScript defers the "upgrade" event until the final request-body chunk when the body completed in the initial buffer.
Validate upgrade body and socket paths
test/js/node/http/node-http-upgrade-with-body.test.ts
Tests cover same-chunk and split bodies, tunnel data, chunked requests, fallback buffering, no-body upgrades, incomplete bodies, socket closure, and server shutdown.

Possibly related PRs

  • oven-sh/bun#35974: Both changes adjust node:http upgrade-with-body head slicing and socket data delivery.

Suggested reviewers: cirospaciari, jarred-sumner

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main change: node:http upgrade handling for bodies and FIN close behavior.
Description check ✅ Passed The description covers the problem, fix, and verification, with concrete tests and results, though it uses different headings than the template.

Comment @coderabbitai help to get the list of available commands.

@robobun

robobun commented Jul 26, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 12:21 AM PT - Jul 27th, 2026

❌ @robobun, your commit 57f25c7 has 1 failures in Build #83122 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 35983

That installs a local version of the PR into your bun-35983 executable, so you can run:

bun-35983 --bun

Comment thread src/js/internal/http.ts Resolved
Comment thread src/js/node/_http_server.ts Resolved
Comment thread src/js/node/_http_server.ts Resolved
Comment thread src/js/node/_http_server.ts Resolved
Comment thread src/js/node/_http_server.ts Resolved
Comment thread src/js/node/_http_server.ts Resolved
Comment thread src/js/internal/http.ts Resolved
Comment thread src/js/node/_http_server.ts Resolved
Comment thread src/js/node/_http_server.ts Resolved
@github-actions

Copy link
Copy Markdown
Contributor

@github-actions

Copy link
Copy Markdown
Contributor

Found 1 issue this PR may fix:

  1. node:http is broken for proxies — 4 PRs fixing createConnection, upgrade sockets, connection close, and socket cleanup #28396 - Meta-issue tracking broken node:http proxy behavior; this PR directly addresses the upgrade socket data routing (sub-issue 2) and the server.close() hang from half-open socket leaks on peer FIN (sub-issue 3)

If this is helpful, copy the block below into the PR description to auto-close this issue on merge.

Fixes #28396

🤖 Generated with Claude Code

Comment thread packages/bun-uws/src/HttpParser.h Resolved
robobun added 2 commits July 26, 2026 16:52
… + finally in tests

The head-slice must agree with NodeHTTPResponse__createForJS's has_body
(which excludes HEAD/TRACE) so a HEAD+Upgrade+Content-Length request
keeps every post-header byte in head instead of slicing bytes that no
channel then delivers.

[skip size check]
Comment thread packages/bun-uws/src/HttpParser.h Resolved
Comment thread packages/bun-uws/src/HttpContext.h Resolved
…de the liveness gate

The ConsumeMinimally fallback reassembly parses a view truncated to
maxFallbackSize, so a head sliced from it would not match the full-buffer
remainder the suppression later drops from onSocketData. Clear head there
so JS emits with an empty head and every post-body byte reaches
socket.on('data') like before; add a test covering the split-header +
large-tail shape.

Move the HEAD_PENDING clear inside the returnedData != nullptr gate so it
never touches httpResponseData after onClose has destructed it.

[skip size check]

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/js/node/http/node-http-upgrade-with-body.test.ts`:
- Around line 335-344: Replace the strict stderr equality assertion in this
subprocess test with the established combined assertion pattern used by the
sibling upgrade-body test: continue validating stdout and exitCode, but use an
asymmetric matcher for stderr so benign runtime diagnostics do not fail the
test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 255be053-cfea-47a5-8b22-819bf88f4c86

📥 Commits

Reviewing files that changed from the base of the PR and between 44f6469 and 355ad57.

📒 Files selected for processing (7)
  • packages/bun-uws/src/HttpContext.h
  • packages/bun-uws/src/HttpParser.h
  • packages/bun-uws/src/HttpResponseData.h
  • src/js/internal/http.ts
  • src/js/node/_http_server.ts
  • src/jsc/bindings/NodeHTTP.cpp
  • test/js/node/http/node-http-upgrade-with-body.test.ts

Comment thread packages/bun-uws/src/HttpParser.h Resolved
Comment thread src/js/node/_http_server.ts Resolved
Comment thread packages/bun-uws/src/HttpContext.h Resolved
…r order, drop redundant onSocketData; fix split-header test race on Windows

[skip size check]
Comment thread src/js/node/_http_server.ts Resolved
@robobun

robobun commented Jul 26, 2026 •

Copy link
Copy Markdown
Collaborator Author

Diff is ready at 57f25c7 (simplified: chunked pre-scan dropped, Content-Length only; src/packages 145 → 94 insertions). All 10 tests pass locally on debug (ASAN), release, and Windows debug; all test/js/node/test/parallel/test-http-upgrade-* pass.

CI build 83122: 148 lanes passed, 44 running; failures are s3.test.ts (Cloudflare R2 ServiceUnavailable, external) and a napi.test.ts flake, neither touched by this diff. No node-http-upgrade-with-body.test.ts failures on any lane.

Comment thread src/js/internal/http.ts Resolved
Comment thread packages/bun-uws/src/HttpParser.h Resolved
Comment thread src/js/internal/http.ts Resolved
Comment thread packages/bun-uws/src/HttpParser.h Resolved

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No new findings on 1d2149b, but this touches the HTTP parser's body-framing path and adds a chunked pre-scan that must mirror the real parse's error gates, so it's worth a human look.

Checked this round: the !deferredTransferEncodingError gate now keeps a non-chunked TE from entering the pre-scan; the per-chunk scanExt overflow check and validateNodeTrailerSection gate match the real parse; socket.once('close', resolveUpgrade) runs after the emit like the CONNECT path; the onEnd tunnel-after-body branch defers fin delivery to onClose (no double-dispatch).

Extended reasoning...

Overview

Changes span the uWS HTTP parser (HttpParser.h — new bodyCompleteInHead field and a chunked-body pre-scan that computes the post-body head slice), HttpContext.h (new HTTP_NODE_TUNNEL_HEAD_PENDING bit to suppress one same-chunk onSocketData; onEnd now closes on unsatisfied-body FIN instead of leaving the tunnel half-open), HttpResponseData.h (the new flag), NodeHTTP.cpp (passes bodyCompleteInHead to JS), and the JS side (_http_server.ts wraps the emit in emitUpgrade() and defers it via kDeferredUpgradeEmit; internal/http.ts consumes the deferred emit at the body fin, running emitEOFIncomingMessageOuter synchronously first so req.trailers is populated). ~500 lines across 7 files plus a 356-line test.

Security risks

The pre-scan re-parses the chunked body with local state before the real parse runs, so any gate the real parse enforces but the pre-scan misses would set bodyCompleteInHead=true for a body the real parse then rejects — stranding kDeferredUpgradeEmit until onClose fires it after 'clientError'. Three such gaps were found and fixed across earlier rounds (scanExt per-chunk, validateNodeTrailerSection, deferredTransferEncodingError). I did not find a fourth, but the pre-scan/real-parse lockstep is the fragile invariant here and a human should confirm no other real-parse error path exists between requestHandler return and the fin dataHandler. The head slice arithmetic is bounded by length and gated on remainingStreamingBytes <= length, so no OOB read.

Level of scrutiny

High. This is the node:http request-framing path — the same code that guards against request smuggling — and it introduces a second parse of the same bytes whose result must agree with the first. The onEnd semantic change (close instead of half-open on unsatisfied body) is a behavior change for the tunnel-after-body state.

Other factors

Three prior review rounds each surfaced a real correctness issue that was then fixed; the test file covers the CL/chunked/trailer/split/fallback-buffer/FIN matrix and asserts req.complete/readableLength/trailers at emit time. The PR description reports all test-http-upgrade-* and test-http-connect* still pass. The design choice to pre-scan (rather than, say, delay computing head until after the real parse's fin dispatch) is worth a maintainer's eye — it works, but keeping two chunked-decode passes in agreement is a maintenance cost.

@Jarred-Sumner Jarred-Sumner left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Too much code. Simplify.

The chunked pre-scan had to mirror every gate the real parse applies
before the fin dispatch (per-chunk scanExt, validateNodeTrailerSection,
deferredTransferEncodingError) to stay consistent. Drop it: for a
chunked Upgrade body head is empty and the post-body remainder reaches
socket.on('data'), which is what Node's own upgrade-with-body test
concatenates with head anyway. bodyCompleteInHead is now only set for a
Content-Length body that fits in this chunk, on the fast path.

[skip size check]
@robobun

robobun commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator Author

Simplified in 57f25c7: dropped the chunked-body pre-scan (which had to mirror every gate the real parse applies and was ~35 lines of fragile lockstep). bodyCompleteInHead is now only set for a Content-Length body that completes in this chunk on the fast path; for chunked, head is empty and the post-body remainder reaches socket.on('data') (what Node's own test-http-upgrade-server-with-body-and-extras concatenates with head anyway).

src/packages diff: 145 → 94 insertions. 10/10 tests pass, all test-http-upgrade-* pass, 8/10 fail on the released binary.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The 57f25c7 simplification (CL-only, no chunked pre-scan) resolves the pre-scan/real-parse consistency class I flagged on earlier revisions — the contentLengthStringLen && !transferEncoding.has && remainingStreamingBytes <= length gate now exactly matches what the real parse's CL branch will dispatch as fin=true, so kDeferredUpgradeEmit can't be stranded. No new issues on this revision, but this still spans the parser, onEnd close semantics, and a new state flag threaded through four layers, so worth a human sign-off.

What was checked on 57f25c7:

  • CL-only head-slice gate vs. the real parse's CL dispatch — no divergence remains; !ConsumeMinimally correctly excludes the fallback-buffer path.
  • emitUpgrade() ordering: once('close', resolveUpgrade) after emit (matches CONNECT); the !handled branch now returns upgradePromise instead of undefined, but destroy→close settles it.
  • onEnd TUNNEL_AFTER_BODY: close-only, onClose delivers both fins; HEAD_PENDING clear scoped inside the returnedData != nullptr block.
  • HEAD/TRACE exclusion is conservative (falls back to pre-PR behavior); has_body computation in NodeHTTPResponse.rs agrees with the JS branch selection.
Extended reasoning...

Overview

Node-compat fix for 'upgrade' with a request body: the head argument becomes the post-body remainder of the same TCP chunk (Content-Length only), req.complete/readableLength are populated before the listener runs via a deferred emit from onDataIncomingMessage's fin, and a peer FIN with an unsatisfied body now closes the tunnel-after-body socket instead of leaving it half-open. Touches HttpParser.h (new bodyCompleteInHead field + CL arithmetic on req->head), HttpContext.h (new HTTP_NODE_TUNNEL_HEAD_PENDING suppression flag, onEnd close for unsatisfied body), HttpResponseData.h (flag definition), NodeHTTP.cpp (pass bodyCompleteInHead through), internal/http.ts + _http_server.ts (deferred emit + head selection), plus a 356-line test file.

Review history

This is my fourth look. Earlier rounds flagged (all resolved): emit/close-listener ordering regression, redundant onSocketData in onEnd, and three pre-scan/real-parse consistency gaps in the chunked pre-scan (validateNodeTrailerSection, per-chunk scanExt, deferredTransferEncodingError). 57f25c7 dropped the chunked pre-scan entirely, which structurally eliminates that class — bodyCompleteInHead is now only set when the CL arithmetic guarantees the real parse's else if (contentLengthStringLen) branch will call dataHandler(..., true) on the same chunk.

Security risks

The head-slice touches fenceAndConsumePostPadded, but is gated on IsNodeHttp && !ConsumeMinimally and only reads remainingStreamingBytes (already validated <= STATE_SIZE_MASK) against length; the (unsigned int) narrowing of skip is safe because skip <= length and length is unsigned int. No new untrusted-input parsing. The onEnd change makes an unsatisfied-body FIN more aggressive (close instead of half-open), not less.

Level of scrutiny

High — HTTP parser and connection-lifecycle changes, even narrowly gated, warrant maintainer sign-off. The specific items I'd want a human to confirm: (1) the semantic decision to only pre-compute head for CL bodies (chunked remainder now goes to socket.on('data') instead — the PR argues Node's own test concatenates them anyway); (2) the onEnd close for HTTP_NODE_TUNNEL_AFTER_BODY — this is a behavior change from half-open to close for a state that was previously grouped with isConnectRequest; (3) the emitEOFIncomingMessageOuter synchronous call in the deferred branch (bypasses the nextTick that the non-deferred path uses).

Other factors

10 tests, all use test.concurrent, wire error/clientError to reject, release resources in try/finally, and the split-header test's once(server,'connection') race was fixed. The evidence shows 8/10 fail on the released binary and all pass on debug+ASAN. All test-http-upgrade-* and test-http-connect* continue to pass per the PR description.

@robobun

robobun commented Oct 12, 2026

Copy link
Copy Markdown
Collaborator Author

Superseded by #45058.

That change emits 'upgrade' when uWS has parsed the read that carried the head. It covers Content-Length and chunked bodies and adds no check to the parser. The listener gets req.complete === true, the body in req, and the rest of the read as head.

The second half of this pull request, the close on a FIN that comes before the end of the body, is not in #45058.

@robobun robobun closed this Oct 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants