Repository navigation
Conversation
|
Warning Review limit reachedYour included review limit has been reached. You’re in a promotional period — use the checkbox below to run this review for free:
On-demand reviews are free for the next 29 days. After that, they cost $0.25 per reviewed file. How can I continue?Run this review now using the option above, or comment You can also wait for the limit to reset (next review available in 11 minutes), then comment An organization admin can change what happens after included review limits in Billing. How do review limits work?CodeRabbit enforces per-developer PR review limits within each organization. For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (2)
Comment |
|
Status: reproduced on 1.4.0 with a raw socket sending The diff is green. The new
Nothing in the failure set touches Bun.serve, uWS, or |
There was a problem hiding this comment.
I reviewed this PR and didn't find any bugs. The fix is small and reuses the existing HTTP_CONNECTION_CLOSE mechanism, but it changes user-visible Bun.serve behavior in the core uWS request dispatch and the description notes a competing approach in #34261 — a maintainer should confirm the respond-then-close design is the one to land.
What was reviewed:
- Confirmed
isConnectRequestis set bygetHeadersbefore the request handler runs, so the flag is live when the new check reads it. - Checked that on
!IsNodeHttpa CONNECT can only reach the non-pipelinedelsebranch (the pipelined branch force-closes), so the placement covers every dispatch. - Verified the node:http instantiation is untouched (
if constexpr (!IsNodeHttp)) and its tunnel/close paths inonClose/onEnd/the data handler still key offisConnectRequestunchanged. - Tests await the server-side close (not a sleep), wire
end/close/errorto resolve, and assert exactly one response;idleTimeout: 255rules out timeout as the close cause.
Extended reasoning...
Overview
The PR adds a 5-line guarded block to packages/bun-uws/src/HttpContext.h inside the request-dispatch lambda: on a Bun.serve context (!IsNodeHttp), when the parser has marked the connection as a CONNECT request, set HTTP_CONNECTION_CLOSE on the response state. This is the same flag the two lines directly above set for HTTP/1.0 and Connection: close requests, so all downstream close gates (post-parse in onData, internalEnd, onWritable) already handle it. A new describe.concurrent block in test/js/bun/http/bun-server.test.ts covers four cases: head in one read, head split across two reads (fallback-buffer path), an async handler that responds after the parse window, and a routes-only server with no fetch (404 fallback).
Security risks
None introduced. The change closes a connection that was previously kept open as a silent sink; if anything it removes a minor resource-hold vector. No new parsing, no new allocation, no untrusted input reaches new code.
Level of scrutiny
Medium-high. The code change is tiny, mechanical, and gated behind if constexpr (!IsNodeHttp) plus an [[unlikely]] runtime flag, so it cannot affect the common path or node:http. But it lives in the per-request dispatch of the vendored uWS HTTP server — every Bun.serve request flows through this function — and it changes user-visible behavior (connections now close after a CONNECT response). The PR description also explicitly names a competing PR (#34261) that solves the same report by dropping CONNECT before dispatch with no response written; picking respond-then-close over reject-early is a small API/behavior design call that a maintainer should ratify.
Other factors
I traced isConnectRequest: it is written through a bool& by getHeaders in HttpParser.h before requestHandler is invoked, so the new check reads a live value. On !IsNodeHttp the pipelined branch of the dispatch (HTTP_RESPONSE_PENDING) force-closes the socket, so a CONNECT can only ever land in the else branch where the new code sits — no sibling path is missed. The node:http tunnel handling (onSocketData, HTTP_NODE_TUNNEL_AFTER_BODY, onEnd half-open) all sits behind IsNodeHttp or socketData checks and is unaffected. The tests follow the repo's conventions well: port: 0, await using server, awaiting the actual close event with all failure events wired to resolve, idleTimeout: 255 so a passing test cannot be the idle timeout closing the socket, and asserting both the status line and that no second response was parsed from the trailing GET. The PR states the existing node CONNECT tests (node-http-connect.test.ts, regression 25862/32734) still pass.
Given the design-choice aspect and the location in core server dispatch, deferring to a human rather than auto-approving.
Once the parser has seen a CONNECT request-line it stops parsing the connection as HTTP (isConnectRequest). node:http uses that to hand the connection to its 'connect' listener, but Bun.serve has nothing to hand a tunnel to: the request went through the normal handler, the response was written, and the connection was then kept alive in tunnel mode, where every byte the client sent next was discarded. Mark the connection close-after-response when a CONNECT is dispatched on a Bun.serve (non node:http) context, the same state a Connection: close request sets, so the existing close gates shut the socket once the handler's response has been flushed.
fa78356 to
40c6355
Compare
|
Updated 6:25 PM PT - Aug 22nd, 2026
❌ @robobun, your commit 0d11782 has some failures in 🧪 To try this PR locally: bunx bun-pr 37585That installs a local version of the PR into your bun-37585 --bun |
There was a problem hiding this comment.
LGTM — small, well-scoped fix that reuses the existing HTTP_CONNECTION_CLOSE mechanism for CONNECT on Bun.serve.
What was reviewed:
- Verified
isConnectRequestis latched inHttpParser.h::getHeadersbefore the request handler runs, so the flag is set when the new check reads it. - Confirmed the new block sits after
resetResponseState()and inside the!IsNodeHttpnon-pipelined branch; the pipelined branch already closes for Bun.serve, and node:http is compile-time excluded. - Traced
HTTP_CONNECTION_CLOSEthrough the existing close gates (onDatapost-parse,closeIfDoneAndMarked/internalEnd,onWritable) — the async-response test case is covered byinternalEnd. - Tests await the server-initiated close with
idleTimeout: 255, so a pass can only come from the fix; they cover single-read, split-head (fallback buffer), async handler, and the routes-only 404 path.
Extended reasoning...
Overview
The PR adds a 5-line block to packages/bun-uws/src/HttpContext.h inside the per-request dispatch lambda: when !IsNodeHttp and httpResponseData->isConnectRequest is set, it ORs HTTP_CONNECTION_CLOSE into the response state. This is placed immediately after the identical treatment for HTTP/1.0 and Connection: close requests, and reuses the same close machinery (shouldCloseConnection() gates in onData, onWritable, and HttpResponse::closeIfDoneAndMarked). A new describe.concurrent("CONNECT requests") block in test/js/bun/http/bun-server.test.ts adds four tests.
Security risks
None introduced. The change strictly narrows behavior: a connection that previously stayed open indefinitely as a silent sink after a CONNECT response now closes once the response flushes. This is a resource-cleanup improvement, not new attack surface. The node:http path is unchanged (compile-time gated by if constexpr (!IsNodeHttp)), so its 'connect' tunnel semantics are unaffected.
Level of scrutiny
Medium. HttpContext.h is core server dispatch, but the change is (a) compile-time gated to Bun.serve only, (b) runtime-gated to isConnectRequest only (marked [[unlikely]]), and (c) purely additive — it sets a flag whose downstream handling is already exercised by every HTTP/1.0 and Connection: close request. I traced that isConnectRequest is set in HttpParser.h::getHeaders before requestHandler is invoked, so the flag is live at the check. resetResponseState() runs just before the new block, so the flag can't be cleared out from under it, and after a CONNECT the parser stops treating the connection as HTTP so no subsequent resetResponseState() will clear it either.
Other factors
The tests are well-constructed per repo guidelines: port: 0, await using for server/socket cleanup, describe.concurrent, awaiting the actual observable condition (server-initiated end/close) rather than sleeping, and idleTimeout: 255 so a passing test proves the close came from the fix rather than idle timeout. The four cases cover the single-read path, the split-head fallback-buffer path, an async handler (response written outside the parse window, so internalEnd's close gate is what fires), and the no-fetch 404 fallback. The PR notes the node:http CONNECT tests (node-http-connect.test.ts, regressions 25862/32734) still pass. The double-setImmediate between split-write chunks is a write-spacing device, not a condition wait, and is commented as such.
There was a problem hiding this comment.
LGTM — a five-line, well-scoped fix that reuses the existing HTTP_CONNECTION_CLOSE mechanism, gated to !IsNodeHttp.
Checked that httpResponseData->isConnectRequest is set by getHeaders (HttpParser.h:1001) before requestHandler runs (HttpParser.h:1338), so the flag is live when the new block reads it. Placement is after resetResponseState() alongside the HTTP/1.0 / Connection: close cases that already set the same bit, so the existing close gates in onData/internalEnd/onWritable handle the shutdown. The if constexpr (!IsNodeHttp) guard leaves node:http's CONNECT tunnel path untouched. Tests cover fetch handler (sync and deferred response), routes-only 404 fallback, and the split-head fallback-buffer path, each asserting the server ends the connection and no second response is written for the pipelined GET.
Extended reasoning...
Overview
Two files: a five-effective-line addition in packages/bun-uws/src/HttpContext.h (inside the request-dispatch lambda) that ORs HTTP_CONNECTION_CLOSE into the response state when isConnectRequest is set on a Bun.serve (!IsNodeHttp) context, plus a new describe.concurrent("CONNECT requests") block in test/js/bun/http/bun-server.test.ts with four cases.
Security risks
None introduced. If anything this tightens behaviour: previously a CONNECT to Bun.serve left the connection open in tunnel mode as a silent sink (bytes discarded, socket held until idle timeout). Closing after the response removes that resource-hold path. The change does not touch parsing, validation, TLS, or the node:http CONNECT/Upgrade tunnel handling.
Level of scrutiny
The file is a hot path, but the change itself is mechanical: it adds one more case to the existing set of "mark this response as the connection's last" (HTTP/1.0, Connection: close) that already set the same flag a few lines above. I traced that isConnectRequest is written by the parser before the request handler callback runs, that the new block sits after resetResponseState() in the non-pipelined branch (a CONNECT is always the connection's first and only dispatched request, so the pipelined branch is unreachable here), and that if constexpr (!IsNodeHttp) compiles the block out of the node:http instantiation entirely — its 'connect'-listener tunnel handling in _http_server.ts is unaffected.
Other factors
The tests are well-designed per the repo's review rules: they await the server-side close (wiring end/close/error all to resolve), use port: 0 with a raised idleTimeout so a close can only come from the response path, cover the variant matrix (sync fetch, async fetch responding from a later tick, routes-only 404, single-read vs split-head fallback buffer), and assert both the status line and that the pipelined GET produced no second response. The PR description confirms the node:http CONNECT suites (node-http-connect.test.ts, regressions 25862/32734) still pass. PR #34261 is noted as taking a different approach (drop before dispatch, no response); this PR's respond-then-close is the more informative behaviour for clients and the smaller diff, and the description states the case for it — no open design question I'd block on.
Repro
Cause
HttpParser.hsets the per-connectionisConnectRequestflag when it parses aCONNECTrequest-line and from then on stops parsing the connection as HTTP: after the dispatch it marks the rest of the buffer consumed, and every later read goes straight to the data handler (consumePostPadded), which on a Bun.serve connection has noinStreamto deliver to. node:http relies on this to hand the connection to its'connect'listener as a tunnel. Bun.serve has nothing to hand a tunnel to: the request went through the normal handler (fetch, routes, or the 404 fallback), the response was written, and the connection was then kept alive in tunnel mode as a sink.Fix
In the uWS request dispatch (
HttpContext.h), when a CONNECT is dispatched on a Bun.serve context (!IsNodeHttp), setHTTP_CONNECTION_CLOSEon the connection, the same state an HTTP/1.0 orConnection: closerequest sets a few lines above. The handler still answers however it likes, and the existing close gates (post-parse inonData,internalEnd,onWritable) close the socket once that response has been flushed. Every Bun.serve response path already feeds this state into itsend()call, so this coversfetch,routes, static/file routes and the no-handler 404 alike. The node:http instantiation is unchanged (its CONNECT handling lives in_http_server.ts:'connect'listener, or close when there is none).As with the other close-marked requests on Bun.serve, no
Connection: closeheader is added; the response is complete and the socket is closed behind it.#34261 addresses the same report differently, by dropping CONNECT before dispatch with no response written. This PR keeps the response and closes afterwards.
Verification
test/js/bun/http/bun-server.test.ts, newCONNECT requestsblock. Each case sends a CONNECT head followed by a pipelinedGETon a server with a long idle timeout and waits for the server to end the connection, then checks that exactly one response (the handler's) came back:fetch(404 fallback)Without the change all four time out waiting for the close (the 404/405 arrives, the connection stays open); with it they pass.
test/js/node/http/node-http-connect.test.tsand the node CONNECT regression tests (25862,32734) still pass.no test proof · iteration 2 · platform-specific test(s) that do not run on this machine, deferring to CI, which covers all platforms: test/js/bun/http/bun-server.test.ts