diff --git a/lib/client.js b/lib/client.js index 98adffdf182..b74ffcbf7be 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') @@ -1446,7 +1448,7 @@ function _resume (client, sync) { } if (client[kRunning] > 0 && util.bodyLength(request.body) !== 0 && - (util.isStream(request.body) || util.isAsyncIterable(request.body))) { + (util.isStream(request.body) || util.isAsyncIterable(request.body) || util.isFormDataLike(request.body))) { // Request with stream or iterator body can error while other requests // are inflight and indirectly error those as well. // Ensure this doesn't happen by waiting for inflight @@ -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,21 @@ 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 (body && typeof body.read === 'function') { // Try to read EOF in order to get length. body.read(0) @@ -1501,7 +1520,7 @@ function write (client, request) { const bodyLength = util.bodyLength(body) - let contentLength = bodyLength + contentLength = bodyLength ?? contentLength if (contentLength === null) { contentLength = request.contentLength @@ -1544,6 +1563,7 @@ function write (client, request) { } if (request.aborted) { + util.destroy(body) return false } @@ -2050,6 +2070,16 @@ function writeStream ({ h2stream, body, client, request, socket, contentLength, socket .on('drain', onDrain) .on('error', onFinished) + + if (body.errorEmitted ?? body.errored) { + setImmediate(() => onFinished(body.errored)) + } else if (body.endEmitted ?? body.readableEnded) { + setImmediate(() => onFinished(null)) + } + + if (body.closeEmitted ?? body.closed) { + setImmediate(onClose) + } } async function writeBlob ({ h2stream, body, client, request, socket, contentLength, header, expectsPayload }) { diff --git a/lib/core/request.js b/lib/core/request.js index bee7a47af92..16a1efffe61 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, @@ -182,23 +180,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 7b863067870..1ad0eab89fd 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) {