Bug Description
Over HTTP/2, when a server answers a request carrying Expect: 100-continue with a final response (e.g. 401) instead of 100 (Continue), the request never resolves. undici correctly withholds the request body, but the response is never delivered and the request hangs.
RFC 9110 §10.1.1 permits a server to send a final status determinable from the header fields — an "immediate success, redirect, or error response" — in place of 100 (Continue); the client must then deliver that response and not send the body. The common cases are an early 401/413/417 (and equally a 2xx). undici delivers nothing.
This is HTTP/2-only; HTTP/1.x handles it.
Root cause: in lib/dispatcher/client-h2.js, the request-body write is bound to the stream's 'continue' event. On an early final response, 'continue' never fires, so the client never sends END_STREAM; the stream stays half-open, its 'close' event never fires, and onRequestStreamClose (which finalizes the response) never runs.
Reproducible By
Minimal repro: https://github.com/jeswr/undici-h2-expect-continue-bug (npm install && npm start → FAIL: request hung ... after 5s). Condensed:
const http2 = require('node:http2')
const { Client } = require('undici')
// h2 server: answer 401 from the headers alone, no 100 (Continue)
const server = http2.createSecureServer({ key, cert })
server.on('stream', (stream) => {
stream.respond({ ':status': 401 })
stream.end('denied')
})
server.listen(0, async () => {
const client = new Client(`https://localhost:${server.address().port}`, {
allowH2: true, connect: { rejectUnauthorized: false }
})
// hangs — the 401 is never delivered
const res = await client.request({
path: '/', method: 'POST', body: 'x'.repeat(1024), expectContinue: true
})
console.log(res.statusCode)
})
Expected Behavior
undici delivers the early final response (e.g. 401) and completes the request without sending the body, as it does over HTTP/1.x and as RFC 9110 §10.1.1 requires.
Environment
- undici 8.5.0
- Node.js v25.1.0
- HTTP/2 (
allowH2: true); request with expectContinue: true and a body
I have a candidate fix (close the request stream's writable half on an early final response) with a test; happy to open a PR.
Bug Description
Over HTTP/2, when a server answers a request carrying
Expect: 100-continuewith a final response (e.g.401) instead of100 (Continue), the request never resolves. undici correctly withholds the request body, but the response is never delivered and the request hangs.RFC 9110 §10.1.1 permits a server to send a final status determinable from the header fields — an "immediate success, redirect, or error response" — in place of
100 (Continue); the client must then deliver that response and not send the body. The common cases are an early401/413/417(and equally a2xx). undici delivers nothing.This is HTTP/2-only; HTTP/1.x handles it.
Root cause: in
lib/dispatcher/client-h2.js, the request-body write is bound to the stream's'continue'event. On an early final response,'continue'never fires, so the client never sendsEND_STREAM; the stream stays half-open, its'close'event never fires, andonRequestStreamClose(which finalizes the response) never runs.Reproducible By
Minimal repro: https://github.com/jeswr/undici-h2-expect-continue-bug (
npm install && npm start→FAIL: request hung ...after 5s). Condensed:Expected Behavior
undici delivers the early final response (e.g.
401) and completes the request without sending the body, as it does over HTTP/1.x and as RFC 9110 §10.1.1 requires.Environment
allowH2: true); request withexpectContinue: trueand a bodyI have a candidate fix (close the request stream's writable half on an early final response) with a test; happy to open a PR.