Skip to content

Bun.serve http2: reject the request bytes the HTTP/1 parser rejects - #40676

Merged
Jarred-Sumner merged 1 commit into
mainfrom
farm/e20c0b10/h2-request-validation
Aug 28, 2026
Merged

Jarred-Sumner merged 1 commit into
mainfrom
farm/e20c0b10/h2-request-validation

Conversation

@robobun

@robobun robobun commented Aug 28, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • Over http2: true, an unknown :method (BREW, lowercase get) reaches the handler as req.method === "GET": the any-method route takes it and RequestContext::create maps an unknown method to GET (src/runtime/server/RequestContext.rs:1346).
  • :path accepts HTAB, SP and control bytes, so /sta\ttic reaches the handler and new URL(req.url).pathname is /static. lshpack trims trailing whitespace from field names before validation (lshpack.c:1893), so x-a arrives as x-a. Names with (),;={}" and values with control bytes pass.
  • The HTTP/1.1 parser on the same port rejects all of these.

Fix

  • validPseudoHeaderTarget (packages/bun-uws/src/Utilities.h): :method must be a token, :path has no byte at or below 0x20. validFieldName accepts lowercase token bytes only. validFieldValue rejects control bytes other than HTAB. Each is RST_STREAM PROTOCOL_ERROR, the connection survives.
  • A well-formed method Bun has no Method for gets 501 Not Implemented on its stream before the router runs (Http2Context.h, handleHeaderBlock). The known set is the HTTP/1 parser's strict set, case-sensitive.
  • patches/lshpack/no-name-trim.patch removes the trim. node:http2 and the fetch h2 client already validate names, so they now reject x-a instead of seeing x-a.
  • Verified: test/js/bun/http/serve-http2-protocol.test.ts (26 new cases, 21 fail on main). Also the other h2, h3, node:http2 and fetch h2 suites.

Background

  • The h2 server decodes HPACK with lshpack, validates the list per RFC 9113 §8.3 in Http2Connection::handleHeaderBlock, then routes through the uWS router. The fetch handler sits on the any-method node.
  • The HTTP/1 router registers that handler once per known method, so an unknown method matches nothing and the socket closes. Over h2 the any-method node matched everything.
  • Request.method is a fixed enum. No arbitrary token can reach JS, so an unknown method must be rejected at the protocol layer.
  • The WHATWG URL parser strips HTAB and trims trailing control bytes, which is how /sta\ttic becomes /static.
Notes
  • HTTP/1.1 on the same build, same bytes, over loopback: BREW / HTTP/1.1 closes the socket with no response (no route for the method); \x01 as the method: 400; HTAB, SP or a control byte in the target: 505; trailing SP or a non-token byte in a field name: 400; a control byte in a value: 400. DEL and bytes at or above 0x80 in the target or a value: accepted. h2 accepts those two as well after this change.
  • Not changed here: req.url over h2 is the raw :path, over h1 it is WHATWG-normalized (" becomes %22). Bytes at or above 0x80 in the path fold to U+FFFD on both. OPTIONS * reaches the handler with req.url === "*". HTTP/3 (packages/bun-usockets/src/quic.c) validates field names as tokens but has no pseudo-header validation.
  • The lshpack trim also pushed the shortened name into the dynamic table, so for such a block the decoder's table size accounting no longer matched the encoder's.
  • isKnownMethod is [A-Z-] plus Bun__HTTPMethod__from, which is the HTTP/1 parser's strict method check. Method::which alone accepts get (for new Request("get")), so a lowercase wire method would still have been reported as GET.
  • 30-row probe (each row sent over h2 and as an HTTP/1.1 request line on the same port) is what the test matrix distills. Every row now agrees on accept or reject.

[review] gate passed · iteration 0 · 5 files touched

fails on main (without fix)
ASAN without fix: 20 FAILED
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" "test/js/bun/http/serve-http2-protocol.test.ts"
bun test v1.4.1 (65362b53b)

test/js/bun/http/serve-http2-protocol.test.ts:
(pass) Bun.serve http2 protocol > first frame not SETTINGS → GOAWAY PROTOCOL_ERROR [328.24ms]
(pass) Bun.serve http2 protocol > SETTINGS with bad length → GOAWAY FRAME_SIZE_ERROR [341.53ms]
(pass) Bun.serve http2 protocol > oversized frame → GOAWAY FRAME_SIZE_ERROR [350.87ms]
(pass) Bun.serve http2 protocol > WINDOW_UPDATE of 0 on the connection → GOAWAY PROTOCOL_ERROR [361.85ms]
(pass) Bun.serve http2 protocol > connection WINDOW_UPDATE overflow → GOAWAY FLOW_CONTROL_ERROR [252.45ms]
(pass) Bun.serve http2 protocol > even stream id → GOAWAY PROTOCOL_ERROR [249.08ms]
(pass) Bun.serve http2 protocol > HEADERS interleaved before CONTINUATION → GOAWAY PROTOCOL_ERROR [250.14ms]
(pass) Bun.serve http2 protocol > PUSH_PROMISE from client → GOAWAY PROTOCOL_ERROR [244.23ms]
(pass) Bun.serve http2 protocol > bad preface closes the connection [848.58ms]
(pass) Bun.serve http2 protocol > invalid HPACK → GOA
... (truncated)

release without fix: 192 FAILED
bun test v1.4.1-canary.1 (65362b53b)

test/js/bun/http/serve-http2-protocol.test.ts:
error: The session has been destroyed
 code: "ERR_HTTP2_INVALID_SESSION"

      at unknown:1:1
      at destroyWithInvalidSessionNT (node:http2:2399:38)
      at processTicksAndRejections (native:7:39)
278 |   /** Resolve with the first frame matching `pred`, waiting for more data as needed. */
279 |   async waitFor(pred: (f: RawFrame) => boolean): Promise<RawFrame> {
280 |     for (;;) {
281 |       const found = this.frames.find(pred);
282 |       if (found) return found;
283 |       if (this.closed) throw new Error("connection closed before expected frame; got " + this.describe());
                                       ^
error: connection closed before expected frame; got []
      at waitFor (/workspace/bun/test/js/bun/http/serve-http2-helpers.ts:283:34)
278 |   /** Resolve with the first frame matching `pred`, waiting for more data as needed. */
279 |   async waitFor(pred: (f: RawFrame) => boolean): Promise<RawFrame> {
280 |     for (;;) {
281 |       const found = this.frames.find(pred);
282 |       if (found) return found;
283 |       if (this.closed) throw new Error("connect
... (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/bun/http/serve-http2-protocol.test.ts"
bun test v1.4.1 (65362b53b)

test/js/bun/http/serve-http2-protocol.test.ts:
(pass) Bun.serve http2 protocol > first frame not SETTINGS → GOAWAY PROTOCOL_ERROR [469.07ms]
(pass) Bun.serve http2 protocol > SETTINGS with bad length → GOAWAY FRAME_SIZE_ERROR [490.94ms]
(pass) Bun.serve http2 protocol > oversized frame → GOAWAY FRAME_SIZE_ERROR [505.45ms]
(pass) Bun.serve http2 protocol > WINDOW_UPDATE of 0 on the connection → GOAWAY PROTOCOL_ERROR [523.79ms]
(pass) Bun.serve http2 protocol > connection WINDOW_UPDATE overflow → GOAWAY FLOW_CONTROL_ERROR [384.05ms]
(pass) Bun.serve http2 protocol > even stream id → GOAWAY PROTOCOL_ERROR [379.25ms]
(pass) Bun.serve http2 protocol > HEADERS interleaved before CONTINUATION → GOAWAY PROTOCOL_ERROR [382.84ms]
(pass) Bun.serve http2 protocol > PUSH_PROMISE from client → GOAWAY PROTOCOL_ERROR [373.02ms]
(pass) Bun.serve http2 protocol > bad preface closes the connection [1243.89ms]
(pass) Bun.serve http2 protocol > invalid HPACK → GO
... (truncated)

release with fix: all passed
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped)
  target       linux-x64-gnu
  build type   Release
  build dir    ./build/release
  revision     f5b2098b0c
  features     baseline

23 deps, 131 codegen, 1172 objects in 970ms

ninja: Entering directory `/workspace/bun/build/release'
[1/1244] install /workspace/bun
bun install v1.4.1-canary.1 (65362b53b)

Checked 26 installs across 63 packages (no changes) [22.00ms]
[2/1244] gen bindgenv2
[3/1244] gen ErrorCode+*.h
[4/1244] gen .bind.ts → GeneratedBindings.cpp
[5/1244] install /workspace/bun/packages/bun-error
bun install v1.4.1-canary.1 (65362b53b)

Checked 1 install across 2 packages (no changes) [1.00ms]
[6/1244] gen bake.{client,server,error}.js
-> bake.client.js, bake.server.js, bake.error.js
[7/1244] fetch libjpeg-turbo
[libjpeg-turbo] up to date
[8/1217] fetch zlib
[zlib] up to date
[9/1217] fetch tinycc
[tinycc] up to date
[10/1216] install /workspace/bun/src/node-fallbacks
bun install v1.4.1-canary.1 (65362b53b)

Checked 111 installs across 104 packages (no changes) [31.00ms]
[11/1216] gen ProcessBindingConstants.lut.h
Generating /workspace/bun/build/release/codegen/Pro
... (truncated)
diff hotspot
packages/bun-uws/src/Http2Context.h           | 30 ++++++++++++---
 packages/bun-uws/src/Utilities.h              | 25 +++++++++++--
 patches/lshpack/no-name-trim.patch            | 16 ++++++++
 scripts/build/deps/lshpack.ts                 |  6 ++-
 test/js/bun/http/serve-http2-protocol.test.ts | 53 +++++++++++++++++++++++++++
 5 files changed, 120 insertions(+), 10 deletions(-)

gate history · 1 passed · 0 rejected · iteration 0

evidence per changed file
file                                           reads  edits  tests
packages/bun-uws/src/Http2Context.h                3      7      0
packages/bun-uws/src/Utilities.h                   2      2      0
patches/lshpack/no-name-trim.patch                 0      0      0
scripts/build/deps/lshpack.ts                      2      4      0
test/js/bun/http/serve-http2-protocol.test.ts      4      2      0

Over HTTP/2 an unknown :method reached the handler as GET, :path
accepted control bytes and SP, and field names with trailing whitespace
or non-token bytes reached the handler trimmed or verbatim. The HTTP/1
parser on the same port rejects all of these.

- :method must be a token. A token Bun.serve has no Method for gets 501
  on its stream before the router runs.
- :path rejects bytes at or below 0x20. Field values reject control
  bytes other than HTAB. Field names must be lowercase tokens.
- lshpack no longer trims trailing whitespace from literal field names
  (patches/lshpack/no-name-trim.patch), so the validators see the bytes
  the peer sent.
@coderabbitai

coderabbitai Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

  • Run on-demand review

On-demand reviews are free for the next 24 days. After that, they cost $0.25 per reviewed file.

Or wait 59 seconds for your next included review.

View limit details

Limit details: You’ve used all 5 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 543ab2ce-27af-4098-a3fd-48a1ea1b19de

📥 Commits

Reviewing files that changed from the base of the PR and between 43fad9b and f5b2098.

📒 Files selected for processing (5)
  • packages/bun-uws/src/Http2Context.h
  • packages/bun-uws/src/Utilities.h
  • patches/lshpack/no-name-trim.patch
  • scripts/build/deps/lshpack.ts
  • test/js/bun/http/serve-http2-protocol.test.ts

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

@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.

I reviewed this PR and didn't find any bugs. Because it tightens request-byte validation on the network-facing HTTP/2 hot path and patches vendored lshpack in a way that also reaches node:http2 and the fetch h2 client, a human look is still worthwhile.

What was reviewed:

  • validFieldName / validFieldValue / validPseudoHeaderTarget edge cases — empty name, bare ":", and CONNECT (empty :path) all still take the intended branch.
  • isKnownMethod gating in handleHeaderBlock — the 501 path creates and registers the stream before writeStatus()->end(), matching the existing 404-after-route lifecycle, and CONNECT/M-SEARCH pass the [A-Z-] prefilter.
  • The lshpack patch removes only the trailing-whitespace trim; the len == 0 guard is preserved.
  • New tests follow the file's existing raw-frame pattern and each asserts the connection survives on a follow-up stream.
Extended reasoning...

Overview

This PR tightens HTTP/2 request-header validation in packages/bun-uws/src/Http2Context.h and Utilities.h to match RFC 9113 §8.2.1 and align with what the HTTP/1.1 parser on the same port already rejects: :method must be a non-empty token, :path may not carry bytes ≤ 0x20, field names must be lowercase RFC 9110 tokens, and field values reject all C0 controls except HTAB. A new isKnownMethod check answers 501 for token-valid but unrepresentable methods before the router runs (previously they reached the "any" handler and were reported as GET). A new vendor patch patches/lshpack/no-name-trim.patch (wired into scripts/build/deps/lshpack.ts) removes upstream lshpack's trailing-whitespace trim on decoded literal header names so names like "x-a " reach the validator byte-exact. 26 new cases are added to test/js/bun/http/serve-http2-protocol.test.ts.

Security risks

This is protocol-level input validation on a network-facing server — squarely security-sensitive. The direction of the change is strictly tightening (more inputs rejected, fail-closed via RST_STREAM PROTOCOL_ERROR), which reduces rather than adds attack surface, and the request-smuggling-adjacent aliasing (/sta\ttic → /static, x-a → x-a, unknown method → GET) it closes is a real class. However, the lshpack patch changes decoder behavior seen by every HPACK consumer in the process (Bun.serve h2, node:http2, fetch h2 client), and the PR relies on those other paths already validating names to absorb the behavior change. That cross-subsystem reach is the main reason a human should confirm the other h2 suites still pass.

Level of scrutiny

High. Per the repository's review rules, changes to security-sensitive parsing of untrusted input, hot-path header validation, and vendored-dependency patches each independently warrant human review. The change is not mechanical: it introduces a new user-visible behavior (501 for unknown :method), a new shared helper (isTokenByte), and a permanent divergence from upstream lshpack.

Other factors

No CODEOWNERS entry covers these paths. The bug hunt exited on dry_streak with no findings. Test coverage is thorough — malformed cases each assert both the RST_STREAM and connection survival, the 501 loop covers lowercase/mixed-case/underscore variants, and a positive case pins every tchar and HTAB-in-value as still accepted. I checked the trickier control-flow edges (unsigned i = n && p[0] == ':' handles n==0 and bare ":"; CONNECT skips the new :path byte scan; the 501 branch registers the stream before ending it, mirroring the 404 path) and found nothing wrong, but the combination of protocol-layer semantics and a vendored-dep patch is exactly the kind of change a maintainer should sign off on.

@robobun

robobun commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator Author

On the lshpack patch reaching the other HPACK decoders: the two other consumers validate names after decode, so the only visible change for them is that a peer-sent x-a is now rejected instead of delivered as x-a.

  • node:http2 checks decoded names in is_malformed_field_name (src/runtime/api/bun/h2_frame_parser.rs:491), the same token set as validFieldName here.
  • The fetch h2 client checks them in is_malformed_response_field (src/http/h2_client/dispatch.rs, decode_header_block).

Suites run locally with this branch's debug build, all green: test/js/node/http2/node-http2.test.js, h2-conformance.test.ts, test/js/web/fetch/fetch-http2-adversarial.test.ts, fetch-http2-client.test.ts, test/js/bun/http/serve-protocols.test.ts (534 pass, 6 skip), serve-http2.test.ts, serve-http2-lifecycle.test.ts, serve-http3.test.ts (150 pass), serve-http2-protocol.test.ts (193 pass).

The one red lane in CI so far is compression.test.ts on alpine 3.23 x64, which is the known main break that #40487 fixes.

@robobun

robobun commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 6:14 PM PT - Aug 27th, 2026

❌ @robobun, your commit f5b2098 has 2 failures in Build #107138 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 40676

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

bun-40676 --bun

@robobun

robobun commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator Author

CI on f5b2098: 179 of 181 jobs pass. The two red lanes fail on main as well and do not touch this diff:

fetch-leak.test.ts passed on retry. Every lane that runs the h2, h3, node:http2 and fetch h2 suites is green.

@Jarred-Sumner
Jarred-Sumner merged commit 5900a8d into main Aug 28, 2026
12 of 14 checks passed
@Jarred-Sumner
Jarred-Sumner deleted the farm/e20c0b10/h2-request-validation branch August 28, 2026 04:13
Jarred-Sumner pushed a commit that referenced this pull request Aug 28, 2026
…body, as HTTP/1.1 does (#40692)

### Problem
- Over `http2: true`, `req.url` is `scheme://host` plus the raw `:path`.
The HTTP/1.1 path on the same port runs the request target through the
WHATWG parser. So `/a/../s`, `/./s` and `/%2e/s` reach the handler as
sent over h2 and as `/s` over h1. Anything keyed on `req.url` (a guard,
a logger, a cache) sees a different string per transport for the same
resource. The cause is the eager URL build for MUX requests in
`src/runtime/server/server_body.rs` (`prepare_js_request_context_for`),
which skipped the `bun_url::href_from_string` pass that
`Request::ensure_url` applies for h1.
- A POST, DELETE, OPTIONS or PURGE whose HEADERS frame carries
END_STREAM gets an empty `ReadableStream` as `req.body`. HTTP/1.1 gives
`null` for a request with no Content-Length and no Transfer-Encoding,
including `content-length: 0`. The arming rule was `req_len > 0 || is_te
|| IS_MUX`: every body-method request over h2 or h3 got a pending body.

### Fix
- The MUX URL build now runs the same `href_from_string` normalization
as `ensure_url`, with the same fallback to the raw string when the
parser rejects the input.
- New `uws_h2_res_request_body_ended`
(`src/uws_sys/libuwsockets_h2.cpp`): true when the stream is already
half-closed by the peer or declared `content-length: 0`.
`RespLike::request_body_ended` exposes it, and the arming rule becomes
`req_len > 0 || is_te || (IS_MUX && !request_body_ended)`. HTTP/3
answers `false` (the QUIC FIN is only seen by a later read), so its
behavior is unchanged.
- Correct because a stream that is half-closed by the peer cannot carry
DATA, and `content-length: 0` with a later empty DATA frame only
completes the stream. The C++ layer already rejects END_STREAM with
`content-length > 0`.
- Verified: `test/js/bun/http/serve-http2.test.ts`, two new tests over
TLS and cleartext (4 cases, all fail on main). Each compares h2 against
an HTTP/1.1 request on the same port. Also `serve-http2-protocol`,
`serve-http2-lifecycle`, `serve-http3`, `serve-protocols` (367 pass).

### Background
- `Request.url` for HTTP/1 is computed lazily from the uWS request in
`Request::ensure_url` (`src/runtime/webcore/Request.rs`). HTTP/2 and
HTTP/3 requests populate `url` and `headers` eagerly at dispatch because
the uWS request handle does not outlive the callback.
- `req.body` starts as `BodyValue::Null`. When bytes may arrive, the
server installs a `Locked` pending value and arms the transport's onData
callback. The JS `Request.body` getter reports `null` only for `Null`.
- `Http2Response::remoteClosed` is set from the END_STREAM flag before
the router runs, so the information is available at the point the body
is armed.

<details><summary>Notes</summary>

- The uWS routers (h1 and h2) match on the raw path, so `routes["/s"]`
still does not match `/a/../s` on either transport. That is unchanged
here and identical across transports.
- With `content-length: 0` and no END_STREAM, the handler may answer
before the empty DATA frame arrives. The stream then ends with
RST_STREAM NO_ERROR after the response, the existing early-response
path, and the late DATA frame is ignored.
- The fixture gained a `/body-null` route that returns `String(req.body
=== null)`.
- Follows #40676 (protocol-level validation). This PR is the app-layer
parity part.

</details>

<!-- robobun:evidence:begin -->

---

**[review]** gate passed · iteration 3 · 5 files touched

<details><summary>fails on main (without fix)</summary>

```console
ASAN without fix: 12 FAILED
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" "test/js/bun/http/serve-http2.test.ts"
bun test v1.4.1 (65362b5)

test/js/bun/http/serve-http2.test.ts:
(pass) Bun.serve http2 (TLS + ALPN) > ALPN negotiated h2 [784.52ms]
(pass) Bun.serve http2 (TLS + ALPN) > GET through fetch handler [381.37ms]
(pass) Bun.serve http2 (TLS + ALPN) > POST body is echoed with status and request headers [94.74ms]
(pass) Bun.serve http2 (TLS + ALPN) > POST with END_STREAM on HEADERS (no body) resolves req.text() [33.97ms]
(pass) Bun.serve http2 (TLS + ALPN) > 204 has no body [32.41ms]
(pass) Bun.serve http2 (TLS + ALPN) > HEAD returns content-length and no body [33.91ms]
(pass) Bun.serve http2 (TLS + ALPN) > unknown route is 404 from fetch [27.12ms]
(pass) Bun.serve http2 (TLS + ALPN) > routes: params, per-method, static Response, file route [382.14ms]
(pass) Bun.serve http2 (TLS + ALPN) > request url and headers reach the handler; :authority becomes host [169.53ms]
188 |         ["/%2e/headers", "/headers"],
189 |         ['/headers?q=a"b<c>', "/headers?q=a%22b%3Cc%3E"],
190 |       ]) {
191 |         
... (truncated)

release without fix: 2 FAILED
bun test v1.4.1-canary.1 (7379514)

test/js/bun/http/serve-http2.test.ts:
(pass) Bun.serve http2 (TLS + ALPN) > ALPN negotiated h2 [16.94ms]
(pass) Bun.serve http2 (TLS + ALPN) > GET through fetch handler [4.70ms]
(pass) Bun.serve http2 (TLS + ALPN) > POST body is echoed with status and request headers [1.04ms]
(pass) Bun.serve http2 (TLS + ALPN) > POST with END_STREAM on HEADERS (no body) resolves req.text() [0.46ms]
(pass) Bun.serve http2 (TLS + ALPN) > 204 has no body [0.42ms]
(pass) Bun.serve http2 (TLS + ALPN) > HEAD returns content-length and no body [3.45ms]
(pass) Bun.serve http2 (TLS + ALPN) > unknown route is 404 from fetch [0.38ms]
(pass) Bun.serve http2 (TLS + ALPN) > routes: params, per-method, static Response, file route [29.64ms]
(pass) Bun.serve http2 (TLS + ALPN) > request url and headers reach the handler; :authority becomes host [0.79ms]
(pass) Bun.serve http2 (TLS + ALPN) > req.url is normalized the same way HTTP/1.1 normalizes it on the same port [14.05ms]
(pass) Bun.serve http2 (TLS + ALPN) > req.body is null for a GET whose HEADERS frame carries END_STREAM [0.40ms]
(pass) Bun.serve http2 (TLS + ALPN) > req.body is null for a POST whose HEADE
... (truncated)
```

</details>

<details><summary>passes on PR (with fix)</summary>

```console
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/bun/http/serve-http2.test.ts"
bun test v1.4.1 (65362b5)

test/js/bun/http/serve-http2.test.ts:
(pass) Bun.serve http2 (TLS + ALPN) > ALPN negotiated h2 [455.26ms]
(pass) Bun.serve http2 (TLS + ALPN) > GET through fetch handler [225.53ms]
(pass) Bun.serve http2 (TLS + ALPN) > POST body is echoed with status and request headers [54.93ms]
(pass) Bun.serve http2 (TLS + ALPN) > POST with END_STREAM on HEADERS (no body) resolves req.text() [20.66ms]
(pass) Bun.serve http2 (TLS + ALPN) > 204 has no body [18.46ms]
(pass) Bun.serve http2 (TLS + ALPN) > HEAD returns content-length and no body [19.20ms]
(pass) Bun.serve http2 (TLS + ALPN) > unknown route is 404 from fetch [15.38ms]
(pass) Bun.serve http2 (TLS + ALPN) > routes: params, per-method, static Response, file route [257.66ms]
(pass) Bun.serve http2 (TLS + ALPN) > request url and headers reach the handler; :authority becomes host [79.33ms]
(pass) Bun.serve http2 (TLS + ALPN) > req.url is normalized the same way HTTP/1.1 normalizes it on the same port [323.63ms]
(pass) Bun.serve
... (truncated)

release with fix: all passed
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped)
  target       linux-x64-gnu
  build type   Release
  build dir    ./build/release
  revision     80de99f
  features     baseline

23 deps, 131 codegen, 1172 objects in 843ms

ninja: Entering directory `/workspace/bun/build/release'
[1/143] fetch WebKit (prebuilt)
[WebKit] up to date
[2/143] gen generated_host_exports.rs
generated_host_exports.rs: 122 exports (host=5, lazy=10, generic=107, rust=0); 243 extern-C blocks audited
[3/143] gen cpp.rs (cppbind)
[4/143] gen JS modules (bundle-modules)
Preprocess modules (8418ms)
Bundle modules (198ms)
Postprocesss modules (762ms)
Bundle Functions (876ms)
Generate Code (32ms)

[10.29s] Bundled "src/js" for production
  2595 kb
  197 internal modules
  13 native modules
  50 internal functions across 16 files
[4/143] cargo bun_runtime → libbun_runtime.a
�[1m�[92m   Compiling�[0m bun_core v0.0.0 (/workspace/bun/src/bun_core)
�[1m�[92m   Compiling�[0m bun_runtime v0.0.0 (/workspace/bun/src/runtime)
�[1m�[92m   Compiling�[0m bun_errno v0.0.0 (/workspace/bun/src/errno)
�[1m�[92m   Compiling�[0m bun_ptr v0.0.0 (/workspace/bun/src/ptr)
�[1m�[9
... (truncated)
```

</details>

<details><summary>diff hotspot</summary>

```
src/runtime/server/server_body.rs       | 32 +++++++++---
 src/uws_sys/h2.rs                       |  5 ++
 src/uws_sys/libuwsockets_h2.cpp         |  7 +++
 test/js/bun/http/serve-http2-fixture.ts |  2 +
 test/js/bun/http/serve-http2.test.ts    | 92 +++++++++++++++++++++++++++++++++
 5 files changed, 132 insertions(+), 6 deletions(-)
```

</details>

**gate history** · 5 passed · 0 rejected · iteration 3

<details><summary>evidence per changed file</summary>

```
file                                     reads  edits  tests
src/runtime/server/server_body.rs            9     17      0
src/uws_sys/h2.rs                            6      6      0
src/uws_sys/libuwsockets_h2.cpp              3      3      0
test/js/bun/http/serve-http2-fixture.ts      2      2      0
test/js/bun/http/serve-http2.test.ts         6      5      0
```

</details>

<!-- robobun:evidence:end -->
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