diff --git a/test/fetch/http2.js b/test/fetch/http2.js index 75f0ec08bab..151034b5930 100644 --- a/test/fetch/http2.js +++ b/test/fetch/http2.js @@ -1,6 +1,7 @@ 'use strict' const { createSecureServer } = require('node:http2') +const { createServer } = require('node:http') const { createReadStream, readFileSync } = require('node:fs') const { once } = require('node:events') const { Readable } = require('node:stream') @@ -504,3 +505,77 @@ test('Issue #3046', async (t) => { t.assert.strictEqual(response.headers.get('content-type'), 'text/html; charset=utf-8') t.assert.deepStrictEqual(response.headers.getSetCookie(), ['hello=world', 'foo=bar']) }) + +// The two following tests expose a discrepancy of behavior when enabling HTTP/2. +// Without H2 enabled, empty POST requests have a Content-Length of 0 specified. +// With H2 enabled, empty POST requests do not have a Content-Length header. +// The RFC 9110 (see https://httpwg.org/specs/rfc9110.html#field.content-length) +// states it SHOULD have one, so that is not mandatory, but is there a good reason +// for not having it? +test('[Fetch] Empty POST without h2 has Content-Length', async (t) => { + const server = createServer({ joinDuplicateHeaders: true }, (req, res) => { + res.statusCode = 200 + res.end(`content-length:${req.headers['content-length']}`) + }).listen(0) + + const client = new Client(`http://localhost:${server.address().port}`) + + t.after(async () => { + server.close() + await client.close() + }) + + t.plan(1) + + await once(server, 'listening') + + const response = await fetch( + `http://localhost:${server.address().port}/`, { + method: 'POST', + dispatcher: client + } + ) + + const responseBody = await response.text() + t.assert.strictEqual(responseBody, `content-length:${0}`) +}) + +test('[Fetch] Empty POST with h2 has no Content-Length', async (t) => { + const server = createSecureServer(await pem.generate({ opts: { keySize: 2048 } })) + + server.on('stream', async (stream, headers) => { + stream.respond({ + 'content-type': 'text/plain; charset=utf-8', + ':status': 200 + }) + + stream.end(`content-length:${headers['content-length']}`) + }) + + t.plan(1) + + server.listen() + await once(server, 'listening') + + const client = new Client(`https://localhost:${server.address().port}`, { + connect: { + rejectUnauthorized: false + }, + allowH2: true + }) + + t.after(closeClientAndServerAsPromise(client, server)) + + const response = await fetch( + `https://localhost:${server.address().port}/`, + // Needs to be passed to disable the reject unauthorized + { + method: 'POST', + dispatcher: client + } + ) + + const responseBody = await response.text() + + t.assert.strictEqual(responseBody, `content-length:${undefined}`) +})