Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for multipart/form-data #1606

Merged
merged 10 commits into from
Sep 28, 2022
Merged

feat: add support for multipart/form-data #1606

merged 10 commits into from
Sep 28, 2022

Conversation

cameron-robey
Copy link
Contributor

  • Add support for multipart/form-data

Closes #974

@JacobMGEvans
Copy link

Exciting!

@codecov-commenter
Copy link

codecov-commenter commented Aug 17, 2022

Codecov Report

Merging #1606 (b079acb) into main (fa9fd90) will decrease coverage by 0.64%.
The diff coverage is 92.59%.

@@            Coverage Diff             @@
##             main    #1606      +/-   ##
==========================================
- Coverage   95.21%   94.57%   -0.65%     
==========================================
  Files          50       53       +3     
  Lines        4748     4828      +80     
==========================================
+ Hits         4521     4566      +45     
- Misses        227      262      +35     
Impacted Files Coverage Δ
lib/fetch/body.js 96.46% <92.59%> (-0.65%) ⬇️
lib/dispatcher-base.js 91.48% <0.00%> (-7.23%) ⬇️
lib/core/request.js 96.89% <0.00%> (-3.11%) ⬇️
lib/fetch/file.js 91.76% <0.00%> (-1.10%) ⬇️
lib/client.js 96.98% <0.00%> (-0.78%) ⬇️
lib/core/util.js 97.46% <0.00%> (-0.64%) ⬇️
lib/proxy-agent.js 93.15% <0.00%> (-0.51%) ⬇️
index.js 100.00% <0.00%> (ø)
lib/pool.js 100.00% <0.00%> (ø)
lib/agent.js 100.00% <0.00%> (ø)
... and 14 more

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

Copy link
Member

@ronag ronag left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about errors?

lib/fetch/body.js Show resolved Hide resolved
Copy link
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

busbuy will emit an 'error' event: https://github.com/mscdex/busboy/blob/9aadb7afbcb8c70c81c93b1018313c1b1835afb0/lib/types/multipart.js#L398.

This also needs tests for the error path.

lib/fetch/body.js Outdated Show resolved Hide resolved
Copy link
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

Copy link
Member

@ronag ronag left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CI failure

})
})
busboy.on('error', (err) => {
throw Object.assign(new TypeError(), { cause: err })
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can't just throw here... this will crash the process. Is there tests for this?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can't just throw here... this will crash the process.

Do you have a suggestion?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mcollina
mcollina previously approved these changes Aug 23, 2022
Copy link
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@mcollina mcollina dismissed their stale review August 23, 2022 21:29

test is missing

lib/fetch/body.js Show resolved Hide resolved
@ronag
Copy link
Member

ronag commented Aug 26, 2022

@mcollina I think @KhafraDev is working on a multipart parser without any deps. Should we maybe wait and see how that go before landing this?

@kibertoad
Copy link
Contributor

kibertoad commented Aug 26, 2022

@ronag Do we expect new custom solution to work faster/smoother than a solution that was used across entire Node ecosystem for years?

What would be the criteria for comparison after it is implemented?

@mcollina
Copy link
Member

I would prefer we use busboy at this stage.

@KhafraDev
Copy link
Member

KhafraDev commented Aug 26, 2022

I would prefer we use busboy at this stage.

Yes, this was my sentiment from my comment in #974.

What would be the criteria for comparison after it is implemented?

At minimum it would need to pass all of busboy's tests (similar to what we do with node-fetch tests).

Copy link
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good work, there are a couple of comments to fix and we'd be good for landing.

const base64 = encoding.toLowerCase() === 'base64'
const chunks = []
value.on('data', (chunk) => {
if (base64) chunk = Buffer.from(chunk.toString(), 'base64')
Copy link
Contributor

@repsac-by repsac-by Sep 4, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can't just decrypt a base64 chunk, because the length of the base64 chunk must be a multiple of 4 characters.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand.

Copy link
Contributor

@repsac-by repsac-by Sep 4, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const data = Buffer.from(Buffer.from('hello world').toString('base64'));

const chunks1 = [];
const chunks2 = [];
for (let offset = 0, step = 6; offset < data.length; offset += step) {
	const chunk = data.subarray(offset, offset + step);

	// Decode each chunk
	chunks1.push(Buffer.from(chunk.toString(), 'base64'));

	// Here we collect as is
	chunks2.push(chunk);
}

// Decode after the transfer is completed
const buffer = Buffer.from(Buffer.concat(chunks2).toString(), 'base64');

// hell�v�ld
console.log(await new Blob(chunks1).text());

// hello world
console.log(await new Blob([buffer]).text());

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have the https://nodejs.org/api/string_decoder.html for this purpose.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mcollina does string decoder need to be applied somewhere in this code explicitly, or it will be called down the line automatically?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cameron-robey see what @repsac-by is suggesting, plus test to cover that case. inlining the code should be fine

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the problem exactly? You need 4 bytes to decode base64 at minimum, so why don't you accumulate until you have 4 bytes of multiples?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mcollina I'm assuming that you mean: push chunks until the total length of unprocessed data is divisible by 4, then decode and clear the chunk array, and repeat with the next pieces of data. Accumulating like that seems unreliable, since it cannot be known when that multiple of 4 will be reached. It might happen only after a huge number of chunks. The streaming method suggested above is superior because the number of unprocessed bytes that are kept in memory never exceeds 3.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The one in the comment also looks good.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cameron-robey Do you need any help with the remaining part?

@cameron-robey
Copy link
Contributor Author

Will get to finishing the last couple of things by the end of the week 👍

Copy link
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is mostly good to go.

test/fetch/client-fetch.js Outdated Show resolved Hide resolved
Copy link
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@ronag ronag merged commit 2d38b7e into nodejs:main Sep 28, 2022
@cameron-robey cameron-robey deleted the multipart-formdata branch September 28, 2022 08:53
@kibertoad
Copy link
Contributor

@ronag why did we merge this without addressing all comments? should I create a follow-up PR?

@ronag
Copy link
Member

ronag commented Sep 28, 2022

@kibertoad Please open a follow-up PR.

@RangerMauve
Copy link

Is there somewhere we can track the inclusion of this update into Node?

@kibertoad
Copy link
Contributor

https://github.com/nodejs/node/pulls?q=is%3Apr+undici+is%3Aclosed

metcoder95 pushed a commit to metcoder95/undici that referenced this pull request Dec 26, 2022
* add support for multipart/form-data

* Handle busboy errors

* linting

* Catch emitted error

* reject promise instead of throwing error

* Add test for base64 encoded multipart/form-data

Thanks for the help @mrbbot !

* Move busboy from devDependencies to dependencies

* Add test for busboy emitting error

* Rewrite tests

* Update tests to avoid promises and callbacks
crysmags pushed a commit to crysmags/undici that referenced this pull request Feb 27, 2024
* add support for multipart/form-data

* Handle busboy errors

* linting

* Catch emitted error

* reject promise instead of throwing error

* Add test for base64 encoded multipart/form-data

Thanks for the help @mrbbot !

* Move busboy from devDependencies to dependencies

* Add test for busboy emitting error

* Rewrite tests

* Update tests to avoid promises and callbacks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement multipart parsing