Skip to content

Commit 584e6ae

Browse files
authored
perf(h2): end zero-length request bodies with headers (#5169)
Assisted-by: openai:gpt-5.5 Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
1 parent e2bc508 commit 584e6ae

2 files changed

Lines changed: 57 additions & 2 deletions

File tree

lib/dispatcher/client-h2.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -870,7 +870,7 @@ function writeH2 (client, request) {
870870
}
871871

872872
// TODO(metcoder95): add support for sending trailers
873-
const shouldEndStream = body === null
873+
const shouldEndStream = body === null || contentLength === 0
874874

875875
if (expectContinue) {
876876
headers[HTTP2_HEADER_EXPECT] = '100-continue'

test/http2-body.js

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
const assert = require('node:assert')
44
const { tspl } = require('@matteo.collina/tspl')
55
const { test, after } = require('node:test')
6-
const { createSecureServer } = require('node:http2')
6+
const { createSecureServer, constants } = require('node:http2')
77
const { createReadStream, readFileSync } = require('node:fs')
88
const { once } = require('node:events')
9+
const { Readable } = require('node:stream')
910

1011
const pem = require('@metcoder95/https-pem')
1112

@@ -114,6 +115,60 @@ test('Should send content-length: 0 for empty h2 requests with payload-expecting
114115
await assert.completed
115116
})
116117

118+
test('Should end h2 zero-length request bodies with headers', async t => {
119+
const server = createSecureServer(await pem.generate({ opts: { keySize: 2048 } }))
120+
const requests = new Map()
121+
122+
server.on('stream', (stream, headers, flags) => {
123+
requests.set(headers[':path'], { headers, flags })
124+
stream.respond({ ':status': 200 })
125+
stream.end()
126+
})
127+
128+
after(() => server.close())
129+
await once(server.listen(0), 'listening')
130+
131+
const client = new Client(`https://localhost:${server.address().port}`, {
132+
connect: { rejectUnauthorized: false }
133+
})
134+
after(() => client.close())
135+
136+
const cases = [
137+
{
138+
path: '/buffer',
139+
body: Buffer.alloc(0),
140+
headers: { 'content-length': '0' }
141+
},
142+
{
143+
path: '/blob',
144+
body: new Blob([])
145+
},
146+
{
147+
path: '/stream',
148+
body: Readable.from([]),
149+
headers: { 'content-length': '0' }
150+
}
151+
]
152+
153+
for (const { path, body, headers } of cases) {
154+
const response = await client.request({
155+
path,
156+
method: 'POST',
157+
headers,
158+
body
159+
})
160+
161+
await response.body.text()
162+
}
163+
164+
for (const { path } of cases) {
165+
const request = requests.get(path)
166+
assert.ok(request, `received ${path}`)
167+
assert.strictEqual(request.headers['content-length'], '0')
168+
assert.ok(request.flags & constants.NGHTTP2_FLAG_END_STREAM)
169+
}
170+
})
171+
117172
test('Should handle h2 request with body (string or buffer) - dispatch', async t => {
118173
t = tspl(t, { plan: 7 })
119174

0 commit comments

Comments
 (0)