From ee6176cd2e09853c868bf5bc1a34bf0500963e4d Mon Sep 17 00:00:00 2001 From: Carlos Fuentes <me@metcoder.dev> Date: Fri, 22 Nov 2024 12:00:13 +0100 Subject: [PATCH] fix: sending formdata bodies with http2 (#3863) [backport] (#3866) * fix: sending formdata bodies with http2 (#3863) (cherry picked from commit e49b5751ddfe726ebc6498f07a4af86de319b691) * fix: bad merge --------- Co-authored-by: Khafra <maitken033380023@gmail.com> --- lib/dispatcher/client-h2.js | 15 ++++++++++- test/http2.js | 53 ++++++++++++++++++++++++++++++++++++- 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/lib/dispatcher/client-h2.js b/lib/dispatcher/client-h2.js index 0448fa00736..00084738a1c 100644 --- a/lib/dispatcher/client-h2.js +++ b/lib/dispatcher/client-h2.js @@ -31,6 +31,8 @@ const { const kOpenStreams = Symbol('open streams') +let extractBody + // Experimental let h2ExperimentalWarned = false @@ -260,7 +262,8 @@ function shouldSendContentLength (method) { function writeH2 (client, request) { const session = client[kHTTP2Session] - const { body, method, path, host, upgrade, expectContinue, signal, headers: reqHeaders } = request + const { method, path, host, upgrade, expectContinue, signal, headers: reqHeaders } = request + let { body } = request if (upgrade) { util.errorRequest(client, request, new Error('Upgrade not supported for H2')) @@ -381,6 +384,16 @@ function writeH2 (client, request) { let contentLength = util.bodyLength(body) + if (util.isFormDataLike(body)) { + extractBody ??= require('../web/fetch/body.js').extractBody + + const [bodyStream, contentType] = extractBody(body) + headers['content-type'] = contentType + + body = bodyStream.stream + contentLength = bodyStream.length + } + if (contentLength == null) { contentLength = request.contentLength } diff --git a/test/http2.js b/test/http2.js index 3f7ab3deb24..7d130e670a9 100644 --- a/test/http2.js +++ b/test/http2.js @@ -10,7 +10,7 @@ const { Writable, pipeline, PassThrough, Readable } = require('node:stream') const pem = require('https-pem') -const { Client, Agent } = require('..') +const { Client, Agent, FormData } = require('..') const isGreaterThanv20 = process.versions.node.split('.').map(Number)[0] >= 20 @@ -1442,3 +1442,54 @@ test('#3671 - Graceful close', async (t) => { await t.completed }) + +test('#3803 - sending FormData bodies works', async (t) => { + const assert = tspl(t, { plan: 4 }) + + const server = createSecureServer(pem).listen(0) + server.on('stream', async (stream, headers) => { + const contentLength = Number(headers['content-length']) + + assert.ok(!Number.isNaN(contentLength)) + assert.ok(headers['content-type']?.startsWith('multipart/form-data; boundary=')) + + stream.respond({ ':status': 200 }) + + const fd = await new Response(stream, { + headers: { + 'content-type': headers['content-type'] + } + }).formData() + + assert.deepEqual(fd.get('a'), 'b') + assert.deepEqual(fd.get('c').name, 'e.fgh') + + stream.end() + }) + + await once(server, 'listening') + + const client = new Client(`https://localhost:${server.address().port}`, { + connect: { + rejectUnauthorized: false + }, + allowH2: true + }) + + t.after(async () => { + server.close() + await client.close() + }) + + const fd = new FormData() + fd.set('a', 'b') + fd.set('c', new Blob(['d']), 'e.fgh') + + await client.request({ + path: '/', + method: 'POST', + body: fd + }) + + await assert.completed +})