Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
209 commits
Select commit Hold shift + click to select a range
aaaa057
node:http/https/http2: raise Node v26.3.0 compat to ~93% and sync the…
cirospaciari Jun 22, 2026
a8d3064
node:http/https/http2: raise Node v26.3.0 compat to ~93% and sync the…
cirospaciari Jun 22, 2026
8d1e6a3
node:http: post-pad the request trailer section before the 8-byte fie…
cirospaciari Jun 26, 2026
e22f9d6
node:http: post-pad the request trailer section before the 8-byte fie…
cirospaciari Jun 26, 2026
aafedba
node:http: add a standalone server-timeout-knobs test
cirospaciari Jun 29, 2026
9ebcfa4
node:http: add a standalone server-timeout-knobs test
cirospaciari Jun 29, 2026
92050fb
test: keep the huge-ArrayBuffer structuredClone cases off the OOM killer
cirospaciari Jun 29, 2026
8ce10c8
test: keep the huge-ArrayBuffer structuredClone cases off the OOM killer
cirospaciari Jun 29, 2026
18c1c46
node:http2: restore a client stream's captured empty async context
cirospaciari Jun 29, 2026
bb4a012
node:http2: restore a client stream's captured empty async context
cirospaciari Jun 29, 2026
3946ff4
test: quarantine test-net-connect-memleak on linux-x64-musl
cirospaciari Jun 29, 2026
0e6f8b1
test: quarantine test-net-connect-memleak on linux-x64-musl
cirospaciari Jun 29, 2026
738780e
usockets: don't truncate a hung-up socket's stream at the poll's EOF …
cirospaciari Jun 29, 2026
73cf3b6
usockets: don't truncate a hung-up socket's stream at the poll's EOF …
cirospaciari Jun 29, 2026
c0ce5fe
node:http: do not shut the socket down while the response is still fl…
cirospaciari Jun 30, 2026
cd4aa05
node:http: do not shut the socket down while the response is still fl…
cirospaciari Jun 30, 2026
dd5400e
node:http/http2: validate h2 requests and harden server error paths
cirospaciari Jul 1, 2026
4f0e779
node:http/http2: satisfy the rust lints enforced in CI
cirospaciari Jul 1, 2026
e3090fd
node:http/http2: address review feedback and the src/js lint failure
cirospaciari Jul 1, 2026
e63fd76
node:http2: do not apply the request Host rules to client-received re…
cirospaciari Jul 1, 2026
b429076
node:http2: reject a malformed PUSH_PROMISE as a connection error, li…
cirospaciari Jul 1, 2026
62d8523
node:http2: give the push-promise unit test's promised request its ma…
cirospaciari Jul 1, 2026
3946946
Merge origin/main into claude/node-http-http2-compat
cirospaciari Jul 1, 2026
7aae0b8
Merge origin/main into claude/node-http-http2-compat
cirospaciari Jul 1, 2026
5ab67eb
Merge claude/node-http-http2-compat (origin/main sync) into claude/no…
cirospaciari Jul 1, 2026
aaaef71
Merge origin/main into claude/node-http-http2-compat
cirospaciari Jul 6, 2026
a370101
Merge origin/main into claude/node-http-http2-compat
cirospaciari Jul 6, 2026
6961bb8
node:http2: detach the allowHTTP1 fallback socket when a response fin…
cirospaciari Jul 6, 2026
27ac2f9
node:http2: detach the allowHTTP1 fallback socket when a response fin…
cirospaciari Jul 6, 2026
5e4ac63
Merge claude/node-http-http2-compat (origin/main sync) into claude/no…
cirospaciari Jul 6, 2026
8b4066c
node:http2: refuse over-budget streams with ENHANCE_YOUR_CALM like node
cirospaciari Jul 6, 2026
eabefc4
node:http2: refuse over-budget streams with ENHANCE_YOUR_CALM like node
cirospaciari Jul 6, 2026
02968cf
Merge branch 'job-8ad2a0dd/node-http-http2-compat' into job-8ad2a0dd/…
cirospaciari Jul 6, 2026
c2a68b3
node:http2: treat late frames on evicted locally-initiated streams as…
cirospaciari Jul 6, 2026
cd563f1
node:http2: treat late frames on evicted locally-initiated streams as…
cirospaciari Jul 6, 2026
20c07b3
Merge branch 'job-8ad2a0dd/node-http-http2-compat' into job-8ad2a0dd/…
cirospaciari Jul 6, 2026
d0ed19d
test: don't require an empty stderr from the throwing-handler http fi…
cirospaciari Jul 6, 2026
d43eb06
Merge origin/main into claude/node-http-http2-compat
cirospaciari Jul 7, 2026
13aaf3c
Merge origin/main into claude/node-http-http2-compat
cirospaciari Jul 7, 2026
8bf7e03
Merge branch 'job-8ad2a0dd/node-http-http2-compat' into job-8ad2a0dd/…
cirospaciari Jul 7, 2026
178c628
node:http: fix trailer/EOF parser hangs, leaks and Windows fatal-writ…
alii Jul 7, 2026
cb4f2dd
node:http: fix trailer/EOF parser hangs, leaks and Windows fatal-writ…
alii Jul 7, 2026
e9a8337
http/http2: address review — h2 response/push validation, TE trailing…
alii Jul 7, 2026
ab86b7d
Merge origin/claude/node-http-http2-compat into claude/node-http-http…
alii Jul 7, 2026
9129c09
node:http/http2: fix Node-compat divergences from review
alii Jul 7, 2026
b9b1ae1
node:http/http2: fix Node-compat divergences from review
alii Jul 7, 2026
05988e6
node:http/http2: dedup ERR_INVALID_ARG_TYPE formatter and clarify ser…
alii Jul 7, 2026
93063de
node:http/http2: dedup ERR_INVALID_ARG_TYPE formatter and clarify ser…
alii Jul 7, 2026
9c480b5
Merge claude/node-http-http2-compat into the http hardening branch
cirospaciari Jul 7, 2026
9d760d2
Merge remote-tracking branch 'origin/claude/node-http-http2-compat' i…
cirospaciari Jul 7, 2026
3763778
node:http: trailer/domain coverage + review-fix cleanups
alii Jul 7, 2026
9ab92d4
node:http: trailer/domain coverage + review-fix cleanups
alii Jul 7, 2026
3898852
node:http2: don't gate client close() on receiving the peer's SETTING…
alii Jul 7, 2026
5e249f0
node:http2: don't gate client close() on receiving the peer's SETTING…
alii Jul 7, 2026
c22eec8
node:http: honor server.httpAllowHalfOpen assigned after listen()
cirospaciari Jul 7, 2026
94255ac
node:http: honor server.httpAllowHalfOpen assigned after listen()
cirospaciari Jul 7, 2026
1355715
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 7, 2026
b7b7572
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 7, 2026
f4542e7
node:http: allow the Trailer response header on chunked responses
cirospaciari Jul 7, 2026
a390358
node:http: allow the Trailer response header on chunked responses
cirospaciari Jul 7, 2026
6944eca
node:http: capture request trailers per request, not per connection
cirospaciari Jul 7, 2026
4a8adab
node:http: capture request trailers per request, not per connection
cirospaciari Jul 7, 2026
4a3cf89
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 7, 2026
4d7f32f
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 7, 2026
c1d3957
node:http/http2: fix pipelined 1xx ordering, socket.parser at 'connec…
alii Jul 7, 2026
d7f2781
node:http/http2: fix pipelined 1xx ordering, socket.parser at 'connec…
alii Jul 7, 2026
8e7d70c
node:http/http2: fix pipelined 1xx ordering, socket.parser at 'connec…
alii Jul 7, 2026
b505f79
Merge origin/claude/node-http-http2-compat into claude/node-http-http…
alii Jul 8, 2026
c376074
node:http2: reopen receive window from _read() and clear kOwnsFd in r…
alii Jul 8, 2026
99eac9e
node:http2: reopen receive window from _read() and clear kOwnsFd in r…
alii Jul 8, 2026
61f0b45
node:http2: pair the receive-window reopen with Node's readStop-on-fu…
cirospaciari Jul 8, 2026
30392cc
node:http2: pair the receive-window reopen with Node's readStop-on-fu…
cirospaciari Jul 8, 2026
9c8dd2c
node:http: move node-compat per-connection state out of Bun.serve's H…
cirospaciari Jul 8, 2026
f2d6f54
node:http: move node-compat per-connection state out of Bun.serve's H…
cirospaciari Jul 8, 2026
15b1db2
node:http: compile the node-compat state and handlers out of Bun.serv…
cirospaciari Jul 8, 2026
b0433f5
node:http: compile the node-compat state and handlers out of Bun.serv…
cirospaciari Jul 8, 2026
ebfa237
node:http2: close a finished server stream whose request body was nev…
cirospaciari Jul 8, 2026
e12dfb7
node:http2: close a finished server stream whose request body was nev…
cirospaciari Jul 8, 2026
d2f54aa
node:http2: only auto-close an unread request when its body is actual…
cirospaciari Jul 8, 2026
bb67830
node:http2: only auto-close an unread request when its body is actual…
cirospaciari Jul 8, 2026
dc7d640
node:http2: make the unread-request close race-free; node:http fixes …
cirospaciari Jul 9, 2026
c14281e
node:http2: make the unread-request close race-free; node:http fixes …
cirospaciari Jul 9, 2026
b8e66e2
node:http2: run the settings() callback before emitting 'localSettings'
cirospaciari Jul 9, 2026
bd8fc38
node:http2: run the settings() callback before emitting 'localSettings'
cirospaciari Jul 9, 2026
4990051
node:http2: let a drained request finish before a graceful session de…
cirospaciari Jul 9, 2026
d93f3d9
node:http2: let a drained request finish before a graceful session de…
cirospaciari Jul 9, 2026
2194f30
node:http2: flush settings() and give its ACK a bounded grace on close
cirospaciari Jul 9, 2026
67f943d
node:http2: flush settings() and give its ACK a bounded grace on close
cirospaciari Jul 9, 2026
ac6d1ae
node:http: fail a request whose trailer section is malformed; honor i…
cirospaciari Jul 9, 2026
252aebb
node:http: fail a request whose trailer section is malformed; honor i…
cirospaciari Jul 9, 2026
a47f450
node:http: never dispatch a pipelined request behind Connection: close
cirospaciari Jul 9, 2026
23965a8
node:http: never dispatch a pipelined request behind Connection: close
cirospaciari Jul 9, 2026
d66bf87
Merge remote-tracking branch 'origin/main' into ciro/ci-70030-fix
cirospaciari Jul 9, 2026
46a59e0
Merge remote-tracking branch 'origin/main' into ciro/ci-70030-fix
cirospaciari Jul 9, 2026
8fffce0
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 9, 2026
7095981
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 9, 2026
302a333
document the trailer-validation capacity bound
cirospaciari Jul 9, 2026
7a2fe94
document the trailer-validation capacity bound
cirospaciari Jul 9, 2026
20bbfb0
node:http: server hot-path performance (stacked on #32488) (#33879)
cirospaciari Jul 10, 2026
269c81a
node:http: server hot-path performance (stacked on #32488) (#33879)
cirospaciari Jul 10, 2026
42ba97c
Merge origin/main into claude/node-http-http2-compat
cirospaciari Jul 10, 2026
d49cff1
Merge origin/main into claude/node-http-http2-compat
cirospaciari Jul 10, 2026
fdd1762
Merge branch 'main' into claude/node-http-http2-compat
cirospaciari Jul 10, 2026
5fb4a01
Merge branch 'main' into claude/node-http-http2-compat
cirospaciari Jul 10, 2026
0274b1c
Merge origin/claude/node-http-http2-compat into claude/node-http-http…
cirospaciari Jul 11, 2026
d1dbe3e
Merge branch 'main' into claude/node-http-http2-compat
cirospaciari Jul 11, 2026
74b1081
uws: fold the per-response booleans into the flags word, drop the nod…
cirospaciari Jul 11, 2026
cceac70
paths: return the resolved slice directly
cirospaciari Jul 11, 2026
c2ce18d
test: stop skipping the HTTP/2 test suite
Jarred-Sumner Jul 11, 2026
06e9db8
runtime: build the body-value pool on the heap instead of a stack tem…
cirospaciari Jul 11, 2026
3680876
Merge remote-tracking branch 'origin/main' into claude/node-http-http…
cirospaciari Jul 11, 2026
bc0b464
test: drop vendored http2 tests that depend on Node-internal machinery
cirospaciari Jul 11, 2026
d5ea586
uws: gate the leading-empty-line tolerance behind the node-http parser
cirospaciari Jul 11, 2026
0bd6587
node:http: drop the misleading optional chain on the pipelined queued…
cirospaciari Jul 11, 2026
9a53587
node:http2: ignore ECONNRESET once a GOAWAY has been received
cirospaciari Jul 11, 2026
031ab0b
node:http2: release the parser refs that process.exit() strands
cirospaciari Jul 11, 2026
072d922
worker: release the loop state a terminated worker leaves behind
cirospaciari Jul 13, 2026
79d630d
test: stop adding expectations.txt entries; gate the named-pipe tests…
cirospaciari Jul 13, 2026
dd56a89
uws: drop the last runtime node-http-compat plumbing and the maybe_un…
cirospaciari Jul 13, 2026
9dbfb99
node:http: bound pipelined response buffering; match Node on a missin…
cirospaciari Jul 13, 2026
deb66a0
Merge remote-tracking branch 'origin/main' into claude/node-http-http…
cirospaciari Jul 13, 2026
4d8585e
node:http: report a missing chunk terminator as HPE_STRICT, like llhttp
cirospaciari Jul 13, 2026
a61111d
Fix clippy let_and_return in the h2 write path
cirospaciari Jul 13, 2026
45480cf
node:https: deliver the TLS EOF to the HTTP layer instead of force-cl…
cirospaciari Jul 13, 2026
bd10ee6
node:http2: resume the socket on graceful session close, like Node
cirospaciari Jul 13, 2026
08e091b
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 13, 2026
116577c
node:http2: judge session idleness by a monotonic written counter, li…
cirospaciari Jul 13, 2026
4bee8a2
node:http2: tear the transport down when a native write fails fatally
cirospaciari Jul 13, 2026
adaa711
webcore: release the FileSink refs that process exit strands
cirospaciari Jul 13, 2026
8ef31c8
node:http: count header bytes in a queued pipelined response's output
cirospaciari Jul 13, 2026
d7bfa55
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 13, 2026
9c17084
node:http2: read session write progress from the native handle's counter
cirospaciari Jul 13, 2026
0d1e01e
node:http2: end the socket first and destroy a tick later on error te…
cirospaciari Jul 13, 2026
2d4a23f
test: calibrate the ASAN leak tests for sanitizer overhead; patch mim…
cirospaciari Jul 13, 2026
b4fa757
usockets: retry macOS's racy EPROTOTYPE like libuv instead of reporti…
cirospaciari Jul 13, 2026
8061d7f
usockets: don't resume a paused socket's reads when re-arming for a w…
cirospaciari Jul 13, 2026
07be335
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 13, 2026
f636e4a
node:http2: don't detach the JS wrapper when closing a fatally-failed…
cirospaciari Jul 13, 2026
5cecac8
usockets(windows): request UV_DISCONNECT so a peer reset fires with n…
cirospaciari Jul 13, 2026
e67ff52
node:http2: make session destroy idempotent, like Node
cirospaciari Jul 14, 2026
f61faf4
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 14, 2026
246c1dc
test: staged half-close-mid-upload teardown coverage
cirospaciari Jul 14, 2026
3807122
test: staged PING-flood teardown coverage
cirospaciari Jul 14, 2026
c59f9e9
usockets(windows): treat UV_DISCONNECT as the EOF hint and arm it unc…
cirospaciari Jul 14, 2026
8d03151
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 14, 2026
4b97a68
node:http2: don't latch destroy on the nothing-to-tear-down early-out
cirospaciari Jul 14, 2026
76c1a4b
node:http2: don't re-destroy an already-destroyed stream from the def…
cirospaciari Jul 14, 2026
2663f0f
node:http2: whitelist the errnos that fatally close the transport
cirospaciari Jul 14, 2026
929ac1e
usockets(windows): make UV_DISCONNECT one-shot so a deferred EOF cann…
cirospaciari Jul 14, 2026
55062db
node:http2: treat an error GOAWAY after our own graceful close as mut…
cirospaciari Jul 14, 2026
affdc52
test: pin the default-localhost loopback contract the vendored suites…
cirospaciari Jul 14, 2026
f74f866
usockets(windows): UV_DISCONNECT surfaces as readable only - recv() o…
cirospaciari Jul 14, 2026
326d048
test: give the abort fixtures measured release headroom
cirospaciari Jul 14, 2026
d5d0254
usockets(windows): map UV_DISCONNECT to EOF only for already-shut-dow…
cirospaciari Jul 14, 2026
29d1fe6
usockets(windows): sync poll_cb's header comment with the conditional…
cirospaciari Jul 14, 2026
4768dd3
test: extend the URLSearchParams leak deadline to ASAN like the debug…
cirospaciari Jul 14, 2026
b4b9de5
node:http2: only skip the deferred error sweep for listener-less dest…
cirospaciari Jul 14, 2026
8763fd9
test: event-taped twins for the two darwin-intermittent h2 scenarios
cirospaciari Jul 14, 2026
507986e
test: probe the PING-flood teardown on both loopback families
cirospaciari Jul 14, 2026
5a312c3
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 14, 2026
af4d380
test: probe the half-close teardown over the default-localhost path too
cirospaciari Jul 14, 2026
8a658d7
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 14, 2026
916afff
test: traced verbatim twin of the half-close teardown in the same run…
cirospaciari Jul 14, 2026
7f1b0f2
test: also probe the PING-flood teardown over the vendored test's exa…
cirospaciari Jul 14, 2026
49e01a3
test: harden the half-close trace and add a 127.0.0.1 sibling in the …
cirospaciari Jul 14, 2026
8905777
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 14, 2026
3db5527
test: half-close trace v3 - a ref'd ticker to test whether the loop t…
cirospaciari Jul 14, 2026
cb1b961
test: turn the v4 half-close trace into the timerless A/B control
cirospaciari Jul 14, 2026
173ca07
test: pin unref'd-timer firing under pure socket-wait
cirospaciari Jul 14, 2026
2a9a332
usockets(windows): give the libuv path a real bun_tick - always poll,…
cirospaciari Jul 14, 2026
b7e7338
usockets(windows): include time.h for struct timespec in the libuv bu…
cirospaciari Jul 14, 2026
671f12e
usockets(windows): us_loop_pump must also bypass uv_run's alive-guard
cirospaciari Jul 14, 2026
c1f6b65
server: keep the graceful-stop ref until the drain completes; revert …
cirospaciari Jul 14, 2026
349b56b
test: un-quarantine the Windows net-reset trio
cirospaciari Jul 14, 2026
662ee8c
Revert the Windows tick and graceful-stop keepalive changes
cirospaciari Jul 14, 2026
759e4b2
test: remove the wedge-investigation traced twins
cirospaciari Jul 14, 2026
0f4370e
node:http server: release request accounting on tunnel handoff; fix w…
cirospaciari Jul 14, 2026
f28abe6
server: keep the graceful-stop ref until the drain completes
cirospaciari Jul 14, 2026
12d4e41
test: traced verbatim twin of the PING-flood test
cirospaciari Jul 14, 2026
209e15c
test: tape the wire frames the raw server receives in the late-RST twin
cirospaciari Jul 14, 2026
6300e69
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 14, 2026
be17e34
Bound the retry of unclassified send() errnos and surface them as errors
cirospaciari Jul 14, 2026
d6a6e43
usockets(windows): deliver peer resets to write-only and paused polls
cirospaciari Jul 14, 2026
deb14ba
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 14, 2026
a4a3e07
fix the libuv abort patch's hunk headers (git-generated against the p…
cirospaciari Jul 14, 2026
4f94249
test: extend the flood tracer's watchdog past done with client-state …
cirospaciari Jul 14, 2026
b4b48f3
usockets(windows): a reset behind buffered data must not defer forever
cirospaciari Jul 14, 2026
bee36cb
node:http2: don't close a transport whose fatal write already drained
cirospaciari Jul 14, 2026
a5ba437
usockets(windows): the paused-probe must follow socket adoption and s…
cirospaciari Jul 14, 2026
776cb49
usockets(windows): sweep-escalate deferred FINs whose peer later reset
cirospaciari Jul 14, 2026
8939a7e
usockets(windows): observe a peer reset with a zero-byte send, not SO…
cirospaciari Jul 14, 2026
4e96ac2
usockets(windows): use raw winsock send for the reset probe (bsd_send…
cirospaciari Jul 14, 2026
312e071
Merge branch 'main' into claude/node-http-http2-compat
cirospaciari Jul 14, 2026
69ac4fd
test: retire the PING-flood tracer - its diagnosis is complete
cirospaciari Jul 14, 2026
223aca6
test: drop test-http2-ping-flood.js - vendored by this branch, unpass…
cirospaciari Jul 14, 2026
c51f8a4
test: give the stream-release check an immediate-drain per GC pass
cirospaciari Jul 14, 2026
a0e2dfb
node:net: server sockets must emit 'error' from native error dispatches
cirospaciari Jul 14, 2026
30dd839
node:net: revert the delegated-error guard bypass - the drain and rea…
cirospaciari Jul 14, 2026
eb693c9
test: stress twin for the worker transfer+terminate ASAN abort
cirospaciari Jul 14, 2026
947cef1
test: scrub upstream-literal markers from newly vendored node tests
robobun Jul 14, 2026
00eaa58
Revert "test: scrub upstream-literal markers from newly vendored node…
cirospaciari Jul 14, 2026
60bce3b
node:net: surface fatal-flush errors on server sockets; scope verify …
robobun Jul 14, 2026
79d4801
ci: retrigger
robobun Jul 15, 2026
eb7b498
Merge remote-tracking branch 'origin/claude/node-http-http2-compat' i…
cirospaciari Jul 15, 2026
93867b2
Merge remote-tracking branch 'origin/main' into claude/node-http-http…
robobun Aug 3, 2026
62a2fec
trim comments to <=3 lines, cite spec/node source
robobun Aug 3, 2026
2256eed
fix build: drop stale mimalloc patch and duplicate Handler.server field
robobun Aug 4, 2026
fbe7dd9
Merge remote-tracking branch 'origin/main' into claude/node-http-http…
robobun Aug 4, 2026
1057479
Merge remote-tracking branch 'origin/main' into claude/node-http-http…
robobun Aug 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions packages/bun-usockets/src/context.c
Original file line number Diff line number Diff line change
Expand Up @@ -822,3 +822,10 @@ struct us_bun_verify_error_t us_socket_verify_error(struct us_socket_t *s) {
}
return (struct us_bun_verify_error_t) { .error = 0, .code = NULL, .reason = NULL };
}

const char *us_socket_sni_servername(struct us_socket_t *s) {
if (s->ssl) {
return us_internal_ssl_sni_servername(s);
}
return NULL;
}
5 changes: 5 additions & 0 deletions packages/bun-usockets/src/crypto/openssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -2496,6 +2496,11 @@ void *us_internal_ssl_sni_userdata(struct us_socket_t *s) {
return us_socket_server_name_userdata(s);
}

const char *us_internal_ssl_sni_servername(struct us_socket_t *s) {
if (!s->ssl || !s_ssl(s)) return NULL;
return SSL_get_servername(s_ssl(s), TLSEXT_NAMETYPE_host_name);
}

void us_internal_listen_socket_ssl_free(struct us_listen_socket_t *ls) {
/* Accepted sockets carry `ls` in per-SSL ex_data so sni_cb can reach the
* listener's SNI tree. Those sockets may outlive the listener (server.close()
Expand Down
1 change: 1 addition & 0 deletions packages/bun-usockets/src/internal/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ int us_internal_ssl_write(us_socket_r s, const char *data, int length);
void *us_internal_ssl_get_native_handle(us_socket_r s);
struct us_bun_verify_error_t us_internal_ssl_verify_error(us_socket_r s);
void *us_internal_ssl_sni_userdata(us_socket_r s);
const char *us_internal_ssl_sni_servername(us_socket_r s);
void us_internal_ssl_handshake_abort(us_socket_r s);
/* SSL_CTX_free(ls->ssl_ctx) + sni_free(ls->sni). Called from us_listen_socket_close. */
void us_internal_listen_socket_ssl_free(struct us_listen_socket_t *ls);
Expand Down
5 changes: 5 additions & 0 deletions packages/bun-usockets/src/libusockets.h
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,11 @@ unsigned char us_connecting_socket_kind(struct us_connecting_socket_t *c) nonnul

struct us_bun_verify_error_t us_socket_verify_error(struct us_socket_t *s);

/* SNI hostname the peer sent in its ClientHello (server-side TLS sockets), or
* NULL when the socket is not TLS or no SNI extension was sent. The returned
* pointer is owned by the SSL object and only valid while the socket lives. */
const char *us_socket_sni_servername(struct us_socket_t *s);

/* ── SSL_CTX construction ─────────────────────────────────────────────────
* The expensive bit (cert/key/CA parse, cipher list, DH params) is decoupled
* from sockets entirely. Build once per SecureContext / config, share across
Expand Down
45 changes: 43 additions & 2 deletions packages/bun-usockets/src/loop.c
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,9 @@ void us_internal_dispatch_ready_poll(struct us_poll_t *p, int error, int eof, in
}

size_t repeat_recv_count = 0;
/* Whether this dispatch's read loop delivered any bytes; see the
* hung-up drain and its error handling below. */
int read_any = 0;

do {
#ifdef _WIN32
Expand Down Expand Up @@ -625,22 +628,36 @@ void us_internal_dispatch_ready_poll(struct us_poll_t *p, int error, int eof, in
if(s && s->flags.adopted && s->prev) {
s = s->prev;
}
read_any = 1;
// loop->num_ready_polls isn't accessible on Windows.
#ifndef WIN32
// rare case: we're reading a lot of data, there's more to be read, and either:
// - the socket has hung up, so we will never get more data from it (only applies to macOS, as macOS will send the event the same tick but Linux will not.)
// - the event loop isn't very busy, so we can read multiple times in a row
#define LOOP_ISNT_VERY_BUSY_THRESHOLD 25
/* Hangup or error flagged on this event (kqueue rides EV_EOF on
* the final data's readable event): no further readable events
* are coming, so drain the kernel buffer now no matter how
* short this read was. Stopping early would let the EOF
* handling below close the socket and discard whatever is
* still queued (a truncated stream). recv() returning 0 or
* EAGAIN ends the loop, so this is bounded by the receive
* buffer. This is what the comment above always described; it
* was keyed on the error flag, which kqueue does not set for
* a peer FIN. */
if (s && !us_socket_is_closed(s) && !s->flags.is_paused && (eof || error)) {
continue;
}
/* Stop if on_data paused us (us_socket_pause from the data
* handler, e.g. fetch() receive backpressure or
* net.Socket#pause) — keep honoring the pause instead of
* pulling bytes the caller asked to defer. */
if (
s && length >= (LIBUS_RECV_BUFFER_LENGTH - 24 * 1024) && length <= LIBUS_RECV_BUFFER_LENGTH &&
(error || loop->num_ready_polls < LOOP_ISNT_VERY_BUSY_THRESHOLD) &&
loop->num_ready_polls < LOOP_ISNT_VERY_BUSY_THRESHOLD &&
!us_socket_is_closed(s) && !s->flags.is_paused
) {
repeat_recv_count += error == 0;
repeat_recv_count++;

// When not hung up, read a maximum of 10 times to avoid starving other sockets
// We don't bother with ioctl(FIONREAD) because we've set MSG_DONTWAIT
Expand Down Expand Up @@ -672,6 +689,18 @@ void us_internal_dispatch_ready_poll(struct us_poll_t *p, int error, int eof, in
eof = 1; // lets handle EOF in the same place
break;
} else if (length == LIBUS_SOCKET_ERROR && !bsd_would_block()) {
if (eof && read_any) {
/* The hangup drain above already delivered this
* connection's final data and then hit the error queued
* behind its FIN (an RST from the peer tearing down right
* after, often provoked by our own teardown writes). The
* orderly EOF was announced and nothing readable remains:
* report end-of-stream like a reader that stopped at the
* FIN, not a read error. A hard error on the FIRST read of
* a hung-up event (a pure RST: the kernel discards the
* receive queue) still takes the error path below. */
break;
}
/* Peer-initiated TCP error (RST etc.) — go straight to
* raw-close. us_socket_close() would route through
* us_internal_ssl_close() now that s->ssl is the
Expand All @@ -690,6 +719,18 @@ void us_internal_dispatch_ready_poll(struct us_poll_t *p, int error, int eof, in
} while (s);
}

/* kqueue reports EV_EOF on the same readable event as a connection's
* final data. If the data callback paused the socket mid-burst (stream
* backpressure), the read loop above stopped with bytes still queued in
* the kernel, and acting on the hint now would end+close the socket and
* discard them. Defer it: resuming re-arms the poll and the EOF is
* re-reported once the rest has been read. Sockets we already shut down
* are exempt (their peer's FIN must still close them promptly below),
* as are error-flagged events. */
if (eof && s && !error && s->flags.is_paused && !us_socket_is_shut_down(s) &&
!us_socket_is_closed(s)) {
eof = 0;
}
if(eof && s) {
if (UNLIKELY(us_socket_is_closed(s))) {
// Do not call on_end after the socket has been closed
Expand Down
20 changes: 16 additions & 4 deletions packages/bun-usockets/src/socket.c
Original file line number Diff line number Diff line change
Expand Up @@ -500,10 +500,22 @@ int us_socket_write_check_error(struct us_socket_t *s, const char *data, int len
us_poll_change(&s->p, s->group->loop, LIBUS_SOCKET_READABLE | LIBUS_SOCKET_WRITABLE);
return 0;
}
/* Fatal send error (EPIPE/ECONNRESET after the peer vanished): report
* it to callers that opt in instead of masking it as would-block, and
* do not keep polling writable - retrying can never succeed. */
if (fatal_write_error) *fatal_write_error = 1;
#ifndef _WIN32
/* libuv treats ENOBUFS like EAGAIN and reports macOS's racy EPROTOTYPE
* as ECONNRESET (vendor/libuv/src/unix/stream.c uv__try_write). */
if (errno == ENOBUFS) {
s->flags.last_write_failed = 1;
us_poll_change(&s->p, s->group->loop, LIBUS_SOCKET_READABLE | LIBUS_SOCKET_WRITABLE);
return 0;
}
#ifdef __APPLE__
if (errno == EPROTOTYPE) errno = ECONNRESET;
#endif
/* Fatal send error: report the errno to callers that opt in and stop
* polling writable - retrying can never succeed. Windows leaves the
* out-param 0 until its WSA-to-errno translation is wired up. */
if (fatal_write_error) *fatal_write_error = errno;
#endif
return 0;
}
if (written != length) {
Expand Down
9 changes: 8 additions & 1 deletion packages/bun-uws/src/App.h
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,12 @@ struct TemplatedApp {
httpContext->getSocketContextData()->onSocketUpgraded = onUpgraded;
}

/* Marks this app as backing a node:http server, enabling the per-socket
* request timing used by headersTimeout/requestTimeout. */
void setUsingNodeHttpCompat(bool value) {
httpContext->getSocketContextData()->flags.usingNodeHttpCompat = value;
}

TemplatedApp &&run() {
uWS::run();
return std::move(*this);
Expand All @@ -767,9 +773,10 @@ struct TemplatedApp {
return std::move(*this);
}

TemplatedApp &&setFlags(bool requireHostHeader, bool useStrictMethodValidation) {
TemplatedApp &&setFlags(bool requireHostHeader, bool useStrictMethodValidation, bool useInsecureHTTPParser) {
httpContext->getSocketContextData()->flags.requireHostHeader = requireHostHeader;
httpContext->getSocketContextData()->flags.useStrictMethodValidation = useStrictMethodValidation;
httpContext->getSocketContextData()->flags.useInsecureHTTPParser = useInsecureHTTPParser;
return std::move(*this);
}

Expand Down
94 changes: 87 additions & 7 deletions packages/bun-uws/src/ChunkedEncoding.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,11 @@ namespace uWS {
* chunk-ext-name = token
* chunk-ext-val = token / quoted-string (TODO: quoted-string unsupported)
*/
inline uint64_t consumeHexNumber(std::string_view &data, uint64_t state) {
/* chunkExtensionsConsumed (optional): incremented for every chunk-extension
* byte consumed; accumulates across every chunk-size line of the body so
* the caller can enforce llhttp's per-message extension limit (node:http
* compat). The caller resets it at the start of each new chunked body. */
inline uint64_t consumeHexNumber(std::string_view &data, uint64_t state, uint64_t *chunkExtensionsConsumed = nullptr) {
/* Resume: '\r' was the last byte of the previous segment. Rare path,
* use data directly to avoid the p/len load on the hot path. */
if (state & STATE_WAITING_FOR_LF) [[unlikely]] {
Expand Down Expand Up @@ -127,6 +131,9 @@ namespace uWS {
}
if (c <= 32) return STATE_IS_ERROR;
state |= STATE_IS_CHUNKED_EXTENSION;
if (chunkExtensionsConsumed) {
++*chunkExtensionsConsumed;
}
}
data = std::string_view(p, len);
return state; /* short read */
Expand All @@ -149,10 +156,72 @@ namespace uWS {
return state == STATE_IS_ERROR;
}

/* node:http compat: parser state for "consuming the trailer section after the
* final 0-size chunk" (RFC 9112 7.1.2). The flag combinations cannot occur in
* any other state: STATE_WAITING_FOR_LF is only ever set while the chunk-size
* line is incomplete, i.e. before STATE_HAS_SIZE is set. _DONE means the
* section (and thus the message) completed and the fin chunk was emitted; the
* next call resets the state without touching the remaining bytes. The
* sub-state must not be derived from the captured section's contents: the fin
* delivery can synchronously run JS that consumes (and clears) the capture. */
constexpr uint64_t STATE_IS_TRAILERS = STATE_HAS_SIZE | STATE_IS_CHUNKED | STATE_WAITING_FOR_LF;
constexpr uint64_t STATE_IS_TRAILERS_DONE = STATE_HAS_SIZE | STATE_WAITING_FOR_LF;

/* Default cap on the raw size of a captured trailer section. Node counts
* trailer bytes against the same max-header-size budget as request headers
* in llhttp, so node:http callers thread the per-server maxHeaderSize
* through; this constant is the fallback for callers that do not. */
constexpr uint64_t MAX_TRAILER_SECTION_SIZE = 16 * 1024;

/* The trailer section is complete once the empty line terminating it has been
* consumed: either the section is empty ("\r\n") or it ends with "\r\n\r\n". */
inline bool isCompleteTrailerSection(const std::string &section) {
size_t n = section.size();
if (n == 2) {
return section[0] == '\r' && section[1] == '\n';
}
return n >= 4 && memcmp(section.data() + n - 4, "\r\n\r\n", 4) == 0;
}

/* Returns next chunk (empty or not), or if all data was consumed, nullopt is returned. */
static std::optional<std::string_view> getNextChunk(std::string_view &data, uint64_t &state, bool trailer = false) {
static std::optional<std::string_view> getNextChunk(std::string_view &data, uint64_t &state, bool trailer = false, uint64_t *chunkExtensionsConsumed = nullptr, std::string *trailerSection = nullptr, uint64_t maxTrailerSectionSize = MAX_TRAILER_SECTION_SIZE) {
/* node:http compat: the previous call emitted the fin chunk for a message
* whose trailer section completed; this call only resets the state and
* leaves the remaining bytes (the next request, or tunnel data) untouched. */
if (trailerSection && state == STATE_IS_TRAILERS_DONE) [[unlikely]] {
state = 0;
return std::nullopt;
}
while (data.length()) {

/* node:http compat: consume the trailer section that follows the final
* 0-size chunk, capturing its raw bytes so the server can populate
* req.trailers. The terminating empty chunk (fin) is only emitted once
* the whole section (including its final CRLF) has been consumed, so
* trailers are always available by the time the message completes. */
if (trailerSection && state == STATE_IS_TRAILERS) [[unlikely]] {
/* A zero cap means the caller has no header-size limit configured (node's
* maxHeaderSize: 0 selects the default, never "unlimited"): the captured
* section must always be bounded or a never-terminating trailer stream OOMs. */
uint64_t trailerSectionCap = maxTrailerSectionSize ? maxTrailerSectionSize : MAX_TRAILER_SECTION_SIZE;
while (data.length()) {
char c = data[0];
trailerSection->push_back(c);
data.remove_prefix(1);
if (trailerSection->size() > trailerSectionCap) [[unlikely]] {
state = STATE_IS_ERROR;
return std::nullopt;
}
if (c == '\n' && isCompleteTrailerSection(*trailerSection)) {
/* Message complete: emit the fin chunk. The next call sees
* STATE_IS_TRAILERS_DONE and resets to 0. */
state = STATE_IS_TRAILERS_DONE;
return std::string_view(nullptr, 0);
}
}
return std::nullopt;
}

// if in "drop trailer mode", consume the terminator bytes after the
// zero-chunk. This is 2 bytes (\r\n) with no trailer, or 4 bytes
// (\r\n\r\n) with trailer=true. Upstream uWS consumed these bytes
Expand Down Expand Up @@ -184,14 +253,22 @@ namespace uWS {
}

if (!hasChunkSize(state)) {
state = consumeHexNumber(data, state);
state = consumeHexNumber(data, state, chunkExtensionsConsumed);
if (isParsingInvalidChunkedEncoding(state)) [[unlikely]] {
return std::nullopt;
}
if (hasChunkSize(state) && chunkSize(state) == 2) {

//printf("Setting state to trailer-parsing and emitting empty chunk\n");

/* node:http compat: the message is not complete until the trailer
* section has been consumed; capture it and emit the fin chunk
* from the trailer-section branch above instead of here. */
if (trailerSection) {
state = STATE_IS_TRAILERS;
continue;
}

// set trailer state and increase size to 4
if (trailer) {
state = 4 /*| STATE_IS_CHUNKED*/ | STATE_HAS_SIZE;
Expand Down Expand Up @@ -306,12 +383,15 @@ namespace uWS {
std::optional<std::string_view> chunk;
uint64_t *state;
bool trailer;
uint64_t *chunkExtensionsConsumed;
std::string *trailerSection;
uint64_t maxTrailerSectionSize;

ChunkIterator(std::string_view *data, uint64_t *state, bool trailer = false) : data(data), state(state), trailer(trailer) {
chunk = uWS::getNextChunk(*data, *state, trailer);
ChunkIterator(std::string_view *data, uint64_t *state, bool trailer = false, uint64_t *chunkExtensionsConsumed = nullptr, std::string *trailerSection = nullptr, uint64_t maxTrailerSectionSize = MAX_TRAILER_SECTION_SIZE) : data(data), state(state), trailer(trailer), chunkExtensionsConsumed(chunkExtensionsConsumed), trailerSection(trailerSection), maxTrailerSectionSize(maxTrailerSectionSize) {
chunk = uWS::getNextChunk(*data, *state, trailer, chunkExtensionsConsumed, trailerSection, maxTrailerSectionSize);
}

ChunkIterator() {
ChunkIterator() : data(nullptr), state(nullptr), trailer(false), chunkExtensionsConsumed(nullptr), trailerSection(nullptr), maxTrailerSectionSize(MAX_TRAILER_SECTION_SIZE) {

}

Expand All @@ -335,7 +415,7 @@ namespace uWS {
}

ChunkIterator &operator++() {
chunk = uWS::getNextChunk(*data, *state, trailer);
chunk = uWS::getNextChunk(*data, *state, trailer, chunkExtensionsConsumed, trailerSection, maxTrailerSectionSize);
return *this;
}

Expand Down
Loading
Loading