From e8c844bffda424f76fd689f62760722d92966885 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jimmy=20Wa=CC=88rting?= Date: Thu, 3 Sep 2020 04:32:47 +0200 Subject: [PATCH 01/12] body conversion and test --- src/body.js | 7 ++++--- test/response.js | 27 +++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/body.js b/src/body.js index f1233034d..637c2555f 100644 --- a/src/body.js +++ b/src/body.js @@ -36,7 +36,7 @@ export default class Body { // Body is undefined or null body = null; } else if (isURLSearchParameters(body)) { - // Body is a URLSearchParams + // Body is a URLSearchParams body = Buffer.from(body.toString()); } else if (isBlob(body)) { // Body is blob @@ -79,7 +79,8 @@ export default class Body { } get body() { - return this[INTERNALS].body; + const {body} = this[INTERNALS]; + return Buffer.isBuffer(body) ? Stream.Readable.from(body) : body; } get bodyUsed() { @@ -327,7 +328,7 @@ export const extractContentType = (body, request) => { * @returns {number | null} */ export const getTotalBytes = request => { - const {body} = request; + const {body} = request[INTERNALS]; // Body is null or undefined if (body === null) { diff --git a/test/response.js b/test/response.js index f02b67f4d..346c0ac97 100644 --- a/test/response.js +++ b/test/response.js @@ -205,4 +205,31 @@ describe('Response', () => { const res = new Response(); expect(res.url).to.equal(''); }); + + it('should cast string to stream using res.body', () => { + const res = new Response('hi'); + expect(res.body).to.be.an.instanceof(stream.Readable); + }); + + it('should cast typed array to stream using res.body', () => { + const res = new Response(Uint8Array.from([97])); + expect(res.body).to.be.an.instanceof(stream.Readable); + }); + + it('should not cast null to stream using res.body', () => { + const res = new Response(null); + expect(res.body).to.be.null; + }); + + it('should cast typed array to text using res.text()', async () => { + const res = new Response(Uint8Array.from([97])); + expect(await res.text()).to.equal('a'); + }); + + it('should cast stream to text using res.text() in a roundabout way', async () => { + const {body} = new Response('a'); + expect(body).to.be.an.instanceof(stream.Readable); + const res = new Response(body); + expect(await res.text()).to.equal('a'); + }); }); From 74c866a252a4dc63de554f87b7c19303863636a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jimmy=20Wa=CC=88rting?= Date: Thu, 3 Sep 2020 04:54:33 +0200 Subject: [PATCH 02/12] also handle blobs --- src/body.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/body.js b/src/body.js index 637c2555f..b13580d62 100644 --- a/src/body.js +++ b/src/body.js @@ -80,7 +80,7 @@ export default class Body { get body() { const {body} = this[INTERNALS]; - return Buffer.isBuffer(body) ? Stream.Readable.from(body) : body; + return Buffer.isBuffer(body) ? Stream.Readable.from(body) : isBlob(body) ? body.stream() : body; } get bodyUsed() { @@ -382,4 +382,3 @@ export const writeToStream = (dest, {body}) => { body.pipe(dest); } }; - From 1aa4486c819139f97ffa5519c76e7a4a1ab39820 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jimmy=20Wa=CC=88rting?= Date: Thu, 3 Sep 2020 04:55:22 +0200 Subject: [PATCH 03/12] typeof null is object --- src/utils/is.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/is.js b/src/utils/is.js index e48165d9f..2fd727a88 100644 --- a/src/utils/is.js +++ b/src/utils/is.js @@ -35,6 +35,7 @@ export const isURLSearchParameters = object => { */ export const isBlob = object => { return ( + object && typeof object === 'object' && typeof object.arrayBuffer === 'function' && typeof object.type === 'string' && From 6131c134c45650fda6a6896d248a3e0d1df3dcb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jimmy=20Wa=CC=88rting?= Date: Thu, 3 Sep 2020 04:55:35 +0200 Subject: [PATCH 04/12] test for blob also --- test/response.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/response.js b/test/response.js index 346c0ac97..e6525aba1 100644 --- a/test/response.js +++ b/test/response.js @@ -216,6 +216,11 @@ describe('Response', () => { expect(res.body).to.be.an.instanceof(stream.Readable); }); + it('should cast blob to stream using res.body', () => { + const res = new Response(new Blob(['a'])); + expect(res.body).to.be.an.instanceof(stream.Readable); + }); + it('should not cast null to stream using res.body', () => { const res = new Response(null); expect(res.body).to.be.null; From aae9e27986106924cbea48bc1c0da16753e4bf9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jimmy=20Wa=CC=88rting?= Date: Thu, 3 Sep 2020 05:05:18 +0200 Subject: [PATCH 05/12] lowercase boundary are easier --- src/body.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/body.js b/src/body.js index b13580d62..edcfb2f53 100644 --- a/src/body.js +++ b/src/body.js @@ -52,7 +52,7 @@ export default class Body { // Body is stream } else if (isFormData(body)) { // Body is an instance of formdata-node - boundary = `NodeFetchFormDataBoundary${getBoundary()}`; + boundary = `nodefetchformdataboundary${getBoundary()}`; body = Stream.Readable.from(formDataIterator(body, boundary)); } else { // None of the above From 1ca26af817a82c49151abead0bd0940ee6c2e8d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jimmy=20Wa=CC=88rting?= Date: Thu, 3 Sep 2020 05:06:40 +0200 Subject: [PATCH 06/12] unreachable code, body should never be a blob or buffer any more. --- src/body.js | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/body.js b/src/body.js index edcfb2f53..f4ff9e210 100644 --- a/src/body.js +++ b/src/body.js @@ -176,16 +176,6 @@ async function consumeBody(data) { return Buffer.alloc(0); } - // Body is blob - if (isBlob(body)) { - body = body.stream(); - } - - // Body is buffer - if (Buffer.isBuffer(body)) { - return body; - } - /* c8 ignore next 3 */ if (!(body instanceof Stream)) { return Buffer.alloc(0); @@ -370,13 +360,6 @@ export const writeToStream = (dest, {body}) => { if (body === null) { // Body is null dest.end(); - } else if (isBlob(body)) { - // Body is Blob - body.stream().pipe(dest); - } else if (Buffer.isBuffer(body)) { - // Body is buffer - dest.write(body); - dest.end(); } else { // Body is stream body.pipe(dest); From 5e2d54a4a69837e8e7e1776f6c1bd72557a6fd0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jimmy=20Wa=CC=88rting?= Date: Fri, 4 Sep 2020 16:25:32 +0200 Subject: [PATCH 07/12] stream singleton --- src/body.js | 18 +++++++++++++----- src/index.js | 4 ++-- src/request.js | 2 +- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/body.js b/src/body.js index f4ff9e210..f1a7d6809 100644 --- a/src/body.js +++ b/src/body.js @@ -60,8 +60,17 @@ export default class Body { body = Buffer.from(String(body)); } + if (Buffer.isBuffer(body)) { + var stream = Stream.Readable.from(body); + } else if (isBlob(body)) { + stream = body.stream(); + } else { + stream = body; + } + this[INTERNALS] = { body, + stream, boundary, disturbed: false, error: null @@ -79,8 +88,7 @@ export default class Body { } get body() { - const {body} = this[INTERNALS]; - return Buffer.isBuffer(body) ? Stream.Readable.from(body) : isBlob(body) ? body.stream() : body; + return this[INTERNALS].stream; } get bodyUsed() { @@ -169,7 +177,7 @@ async function consumeBody(data) { throw data[INTERNALS].error; } - let {body} = data; + const {body} = data; // Body is null if (body === null) { @@ -231,7 +239,7 @@ async function consumeBody(data) { export const clone = (instance, highWaterMark) => { let p1; let p2; - let {body} = instance; + let {body} = instance[INTERNALS]; // Don't allow cloning a used body if (instance.bodyUsed) { @@ -247,7 +255,7 @@ export const clone = (instance, highWaterMark) => { body.pipe(p1); body.pipe(p2); // Set instance body to teed body and return the other teed body - instance[INTERNALS].body = p1; + instance[INTERNALS].stream = p1; body = p2; } diff --git a/src/index.js b/src/index.js index c6cdff0f0..95a577806 100644 --- a/src/index.js +++ b/src/index.js @@ -12,7 +12,7 @@ import zlib from 'zlib'; import Stream, {PassThrough, pipeline as pump} from 'stream'; import dataUriToBuffer from 'data-uri-to-buffer'; -import {writeToStream} from './body.js'; +import {writeToStream, clone} from './body.js'; import Response from './response.js'; import Headers, {fromRawHeaders} from './headers.js'; import Request, {getNodeRequestOptions} from './request.js'; @@ -148,7 +148,7 @@ export default async function fetch(url, options_) { agent: request.agent, compress: request.compress, method: request.method, - body: request.body, + body: clone(request), signal: request.signal }; diff --git a/src/request.js b/src/request.js index 83e5d0a84..9e1375597 100644 --- a/src/request.js +++ b/src/request.js @@ -71,7 +71,7 @@ export default class Request extends Body { if (inputBody !== null && !headers.has('Content-Type')) { const contentType = extractContentType(inputBody, this); if (contentType) { - headers.append('Content-Type', contentType); + headers.set('Content-Type', contentType); } } From 2c4ee22bb793ed225a97cb3b442d0273507fd75e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jimmy=20Wa=CC=88rting?= Date: Fri, 4 Sep 2020 16:27:49 +0200 Subject: [PATCH 08/12] use let --- src/body.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/body.js b/src/body.js index f1a7d6809..2625f4779 100644 --- a/src/body.js +++ b/src/body.js @@ -60,12 +60,12 @@ export default class Body { body = Buffer.from(String(body)); } + let stream = body; + if (Buffer.isBuffer(body)) { - var stream = Stream.Readable.from(body); + stream = Stream.Readable.from(body); } else if (isBlob(body)) { stream = body.stream(); - } else { - stream = body; } this[INTERNALS] = { From d461ce6e2265158b07d2d55c6ddf91451fe8f839 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jimmy=20W=C3=A4rting?= Date: Mon, 13 Sep 2021 11:58:06 +0200 Subject: [PATCH 09/12] typo --- test/response.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/response.js b/test/response.js index caf113c9e..2ea1b44c2 100644 --- a/test/response.js +++ b/test/response.js @@ -238,7 +238,8 @@ describe('Response', () => { expect(body).to.be.an.instanceof(stream.Readable); const res = new Response(body); expect(await res.text()).to.equal('a'); - + }); + it('should support error() static method', () => { const res = Response.error(); expect(res).to.be.an.instanceof(Response); From 1d2db94acaa3427e8b10d804ed2858418d2e6bbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jimmy=20Wa=CC=88rting?= Date: Mon, 13 Sep 2021 12:08:51 +0200 Subject: [PATCH 10/12] convert blob stream into a whatwg stream --- src/body.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/body.js b/src/body.js index 9f582f764..82991eff8 100644 --- a/src/body.js +++ b/src/body.js @@ -65,7 +65,7 @@ export default class Body { if (Buffer.isBuffer(body)) { stream = Stream.Readable.from(body); } else if (isBlob(body)) { - stream = body.stream(); + stream = Stream.Readable.from(body.stream()); } this[INTERNALS] = { From c8318d6c330bb9dedc867ce0b18220c01bebdd51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jimmy=20Wa=CC=88rting?= Date: Mon, 13 Sep 2021 12:11:03 +0200 Subject: [PATCH 11/12] lint fix --- test/response.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/response.js b/test/response.js index 2ea1b44c2..7c3dab5f0 100644 --- a/test/response.js +++ b/test/response.js @@ -239,7 +239,7 @@ describe('Response', () => { const res = new Response(body); expect(await res.text()).to.equal('a'); }); - + it('should support error() static method', () => { const res = Response.error(); expect(res).to.be.an.instanceof(Response); From c621557e86ad4159738d06a32e642a558a66ab7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jimmy=20Wa=CC=88rting?= Date: Mon, 13 Sep 2021 12:44:24 +0200 Subject: [PATCH 12/12] update changelog --- docs/CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 71ec19ae7..4081cf629 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes will be recorded here. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## unreleased + +- other: Deprecated/Discourage [form-data](https://www.npmjs.com/package/form-data) and body.buffer() (#1212) +- fix: Normalize `Body.body` into a `node:stream` (#924) + ## v3.0.0 - other: Marking v3 as stable