From b701a027ca026df1076cf839b9326c7031605464 Mon Sep 17 00:00:00 2001 From: Robert Nagy Date: Sun, 11 Feb 2024 20:46:28 +0100 Subject: [PATCH] fix: handle request body as late as possible --- lib/client.js | 35 +++++++++++++++++++++++++++++++++-- lib/core/request.js | 19 ------------------- lib/core/util.js | 4 ++-- test/client.js | 1 + 4 files changed, 36 insertions(+), 23 deletions(-) diff --git a/lib/client.js b/lib/client.js index 98adffdf182..cfc36672be4 100644 --- a/lib/client.js +++ b/lib/client.js @@ -105,6 +105,8 @@ const { // Experimental let h2ExperimentalWarned = false +let extractBody + const FastBuffer = Buffer[Symbol.species] const kClosedResolve = Symbol('kClosedResolve') @@ -1477,7 +1479,9 @@ function write (client, request) { return } - const { body, method, path, host, upgrade, headers, blocking, reset } = request + const { method, path, host, upgrade, blocking, reset } = request + + let { body, headers, contentLength } = request // https://tools.ietf.org/html/rfc7231#section-4.3.1 // https://tools.ietf.org/html/rfc7231#section-4.3.2 @@ -1494,6 +1498,32 @@ function write (client, request) { method === 'PATCH' ) + if (util.isFormDataLike(body)) { + if (!extractBody) { + extractBody = require('./fetch/body.js').extractBody + } + + const [bodyStream, contentType] = extractBody(body) + if (request.contentType == null) { + headers += `content-type: ${contentType}\r\n` + } + body = bodyStream.stream + contentLength = bodyStream.length + } else if (util.isBlobLike(body) && request.contentType == null && body.type) { + headers += `content-type: ${body.type}\r\n` + } + + if (util.isStream(body) && util.isErrored(body)) { + const err = ( + body.writableErrored ?? + body.readableErrored ?? + body.errored ?? + new RequestAbortedError() + ) + errorRequest(client, request, err) + return false + } + if (body && typeof body.read === 'function') { // Try to read EOF in order to get length. body.read(0) @@ -1501,7 +1531,7 @@ function write (client, request) { const bodyLength = util.bodyLength(body) - let contentLength = bodyLength + contentLength = bodyLength ?? contentLength if (contentLength === null) { contentLength = request.contentLength @@ -1544,6 +1574,7 @@ function write (client, request) { } if (request.aborted) { + util.destroy(body) return false } diff --git a/lib/core/request.js b/lib/core/request.js index 74e0ca16eaa..86033c41a7e 100644 --- a/lib/core/request.js +++ b/lib/core/request.js @@ -26,8 +26,6 @@ const invalidPathRegex = /[^\u0021-\u00ff]/ const kHandler = Symbol('handler') -let extractBody - class Request { constructor (origin, { path, @@ -173,23 +171,6 @@ class Request { throw new InvalidArgumentError('headers must be an object or an array') } - if (util.isFormDataLike(this.body)) { - if (!extractBody) { - extractBody = require('../fetch/body.js').extractBody - } - - const [bodyStream, contentType] = extractBody(body) - if (this.contentType == null) { - this.contentType = contentType - this.headers += `content-type: ${contentType}\r\n` - } - this.body = bodyStream.stream - this.contentLength = bodyStream.length - } else if (util.isBlobLike(body) && this.contentType == null && body.type) { - this.contentType = body.type - this.headers += `content-type: ${body.type}\r\n` - } - util.validateHandler(handler, method, upgrade) this.servername = util.getServerName(this.host) diff --git a/lib/core/util.js b/lib/core/util.js index 55bf9f49822..2c562e6846c 100644 --- a/lib/core/util.js +++ b/lib/core/util.js @@ -182,8 +182,8 @@ function bodyLength (body) { return null } -function isDestroyed (stream) { - return !stream || !!(stream.destroyed || stream[kDestroyed]) +function isDestroyed (body) { + return body && !!(body.destroyed || body[kDestroyed] || (stream.isDestroyed?.(body))) } function isReadableAborted (stream) { diff --git a/test/client.js b/test/client.js index 5aa031f6907..3406d3a9d96 100644 --- a/test/client.js +++ b/test/client.js @@ -1055,6 +1055,7 @@ test('basic POST with empty stream', (t) => { method: 'POST', body }, (err, { statusCode, headers, body }) => { + console.error(err) t.error(err) body .on('data', () => {