Skip to content

fix(h2): forward 1xx informational responses to onInfo - #5712

Open
pacocartones wants to merge 1 commit into
nodejs:mainfrom
pacocartones:fix/h2-forward-1xx-informational
Open

fix(h2): forward 1xx informational responses to onInfo#5712
pacocartones wants to merge 1 commit into
nodejs:mainfrom
pacocartones:fix/h2-forward-1xx-informational

Conversation

@pacocartones

Copy link
Copy Markdown
Contributor

What

onInfo is documented as "Invoked for each informational (1xx) response. May be invoked multiple times." HTTP/1 forwards them — client-h1.js onHeadersComplete forwards a statusCode < 200 via onResponseStart. HTTP/2 does not: it only subscribes to the stream response event (the final response) and never to the headers event, where Node emits informational headers. So 1xx responses (102 Processing, 103 Early Hints) are silently dropped on h2 and onInfo never fires.

Why it matters

103 Early Hints is increasingly used for preload/preconnect, and it is exactly the kind of thing an h2/CDN path serves. Any client.request({ onInfo }) over an ALPN-negotiated h2 connection loses them today, diverging from h1 with no documented reason.

The fix

Subscribe to the stream headers event and forward via onResponseStart, matching the h1 path and reusing the same state == null / aborted || completed guards as onResponse. Only the :status pseudo-header is stripped; 1xx responses carry no other response pseudo-headers.

   stream.on('response', onResponse)
+  stream.on('headers', onInterimResponse)
   stream.on('end', onEnd)

(plus the matching off in the listener teardown, and the onInterimResponse handler).

Test

Adds test/http2-informational.js: an h2 server emits a 103 Early Hints via additionalHeaders before the 200, and the test asserts onInfo receives it with its link header. It fails on main (infos.length 0 vs 1) and passes with the change.

Reachability is plain: allowH2: true (or an Agent negotiating h2) against any server that sends 1xx. No custom store or exotic config.

onInfo is documented as invoked for each 1xx informational response. HTTP/1
forwards them (client-h1.js, statusCode < 200 -> onResponseStart) but HTTP/2
only listens for the stream response event, never the headers event where Node
emits informational headers, so 102/103 are silently dropped on h2 and onInfo
never fires. Subscribe to headers and forward via onResponseStart, matching h1
and the same guards as onResponse; only :status is stripped.

Adds test/http2-informational.js (103 Early Hints): fails before, passes after.
@codecov-commenter

codecov-commenter commented Aug 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 84.61538% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.47%. Comparing base (5033aa1) to head (2b7f612).

Files with missing lines Patch % Lines
lib/dispatcher/client-h2.js 84.61% 4 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main    #5712   +/-   ##
=======================================
  Coverage   93.47%   93.47%           
=======================================
  Files         110      110           
  Lines       38906    38932   +26     
=======================================
+ Hits        36366    36391   +25     
- Misses       2540     2541    +1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants