Skip to content

Commit bc0a19c

Browse files
authored
fix(http2): send body for non-expectsPayload methods with content (#5030)
1 parent 0860142 commit bc0a19c

File tree

2 files changed

+104
-1
lines changed

2 files changed

+104
-1
lines changed

lib/dispatcher/client-h2.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -622,7 +622,7 @@ function writeH2 (client, request) {
622622
contentLength = request.contentLength
623623
}
624624

625-
if (!expectsPayload) {
625+
if (contentLength === 0 && !expectsPayload) {
626626
// https://tools.ietf.org/html/rfc7230#section-3.3.2
627627
// A user agent SHOULD NOT send a Content-Length header field when
628628
// the request message does not contain a payload body and the method

test/http2-body-delete.js

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
'use strict'
2+
3+
const { test, after } = require('node:test')
4+
const { createSecureServer } = require('node:http2')
5+
const { once } = require('node:events')
6+
const assert = require('node:assert')
7+
8+
const pem = require('@metcoder95/https-pem')
9+
10+
const { Client } = require('..')
11+
12+
test('Should handle h2 DELETE request with body', async t => {
13+
const server = createSecureServer(await pem.generate({ opts: { keySize: 2048 } }))
14+
const expectedBody = '{"ids":[1,2,3]}'
15+
const requestBody = []
16+
let contentLengthHeader = null
17+
18+
server.on('stream', async (stream, headers) => {
19+
assert.strictEqual(headers[':method'], 'DELETE')
20+
assert.strictEqual(headers[':path'], '/')
21+
assert.strictEqual(headers[':scheme'], 'https')
22+
23+
contentLengthHeader = headers['content-length']
24+
25+
stream.respond({
26+
'content-type': 'application/json',
27+
':status': 200
28+
})
29+
30+
for await (const chunk of stream) {
31+
requestBody.push(chunk)
32+
}
33+
34+
stream.end(JSON.stringify({ deleted: true }))
35+
})
36+
37+
after(() => server.close())
38+
await once(server.listen(0), 'listening')
39+
40+
const client = new Client(`https://localhost:${server.address().port}`, {
41+
connect: {
42+
rejectUnauthorized: false
43+
},
44+
allowH2: true
45+
})
46+
after(() => client.close())
47+
48+
const response = await client.request({
49+
path: '/',
50+
method: 'DELETE',
51+
body: expectedBody,
52+
headers: {
53+
'content-type': 'application/json'
54+
}
55+
})
56+
57+
assert.strictEqual(response.statusCode, 200)
58+
// Content-Length header should be sent for DELETE with body
59+
assert.strictEqual(contentLengthHeader, String(expectedBody.length))
60+
assert.strictEqual(Buffer.concat(requestBody).toString('utf-8'), expectedBody)
61+
62+
await response.body.dump()
63+
})
64+
65+
test('Should not send Content-Length for h2 DELETE without body', async t => {
66+
const server = createSecureServer(await pem.generate({ opts: { keySize: 2048 } }))
67+
let contentLengthHeader = null
68+
69+
server.on('stream', async (stream, headers) => {
70+
assert.strictEqual(headers[':method'], 'DELETE')
71+
72+
contentLengthHeader = headers['content-length']
73+
74+
stream.respond({
75+
'content-type': 'application/json',
76+
':status': 200
77+
})
78+
79+
stream.end(JSON.stringify({ deleted: true }))
80+
})
81+
82+
after(() => server.close())
83+
await once(server.listen(0), 'listening')
84+
85+
const client = new Client(`https://localhost:${server.address().port}`, {
86+
connect: {
87+
rejectUnauthorized: false
88+
},
89+
allowH2: true
90+
})
91+
after(() => client.close())
92+
93+
const response = await client.request({
94+
path: '/',
95+
method: 'DELETE'
96+
})
97+
98+
assert.strictEqual(response.statusCode, 200)
99+
// Content-Length header should NOT be sent for DELETE without body
100+
assert.strictEqual(contentLengthHeader, undefined)
101+
102+
await response.body.dump()
103+
})

0 commit comments

Comments
 (0)