Open tunnckoCore opened 4 years ago
const server = http.createServer((req, res) => {
if (req.url === '/api/upload' && req.method.toLowerCase() === 'post') {
stream.pipeline(
req,
new MultipartParser(),
async function* consume(source) {
console.log(source.headers);
for await (const chunk of source) {
console.log('chunk:', chunk.toString('utf-8'), '=====');
yield chunk;
}
},
console.error
);
return;
}
// show a file upload form
res.writeHead(200, { 'content-type': 'text/html' });
res.end(`
<h2>With Node.js <code>"http"</code> module</h2>
<form action="/api/upload" enctype="multipart/form-data" method="post">
<div>Text field title: <input type="text" name="title" /></div>
<div>first name: <input type="text" name="first_name" /></div>
<div>File: <input type="file" name="multipleFiles" multiple="multiple" /></div>
<input type="submit" value="Upload" />
</form>
`);
});
server.listen(3000, () => {
console.log('Server listening on http://localhost:3000 ...');
});
It would be nice to have some kind of plugins that handle common use cases when uploading files. Like for example have a plugin to upload a file directly to s3, other to azures cloud blob storage and etc...
Yup, totally cool. I really like this API.
The new year will come with a new design :tada: I cannot believe that a whole year passed and there's still no v2. ;/
We can land something cool in v2 as preparation for v3.
Provide additional exports like formidable/with-aws
, formidable/with-azure
abstractions like
import formidable from 'formidable';
export default (req, res, options = {}) => {
// the code from AWS example
const form = formidable({
...options,
fileWriteStreamHandler: uploadStream,
});
form.parse(req, () => {
res.writeHead(200);
res.end();
});
return form;
}
usage in serverless env (bbut should fix #594 for this to be so clean)
import formidableWithAWS from 'formidable/with-aws';
export default (req, res) => {
formidableWithAWS(req, res, { credentials })
}
As long as it is not included in the default formidable export , I am in favour about adding extensions
Exactly. Built-in "Extensions" (examples, solutions to common things) is good word.
The new year will come with a new design 🎉 I cannot believe that a whole year passed and there's still no v2. ;/
@tunnckoCore Is it going to be switched to v3 without a stable v2?
@Songkeys we will have v2 stable in the latest
dist-tag soon.
@tunnckoCore What prevents us having a stable v2 so far? I'm willing to contribute. But we need a roadmap and a todo-list for stable v2 first, I think. Currently this repo is lack of active maintenance. And many users are still stuck in the 1.X version. Plans and Ideas?
After https://github.com/node-formidable/formidable/pull/689 is merged I will open another PR for the filter branch.
Ideally we fix: the few failing tests (may require some deep untangling of code)
I guess #689 was merged and then the filter PR #716 was also merged, right? I guess that means a v2 release sometime in the next few weeks?
Ah, I guess the current state of v2 is recorded over here, I'll ask over there: https://github.com/node-formidable/formidable/issues/718
One one the first thing I want to do is https://github.com/nodejs/node/issues/38231, this will get rid of all the issues around multiples: true
I created a 3.x branch and opened its first PR for ES Modules
@tunnckoCore about your first comment first example, I think it is confusing that the pipeline function returns a promise and not a stream. Also it would have the same name as stream.pipeline.
In the second example something is out of order: formidable reads request content type to chose the correct parser: json multipart etc. but in your example the parser is chosen before.
What we could do is make the formidable function return a promise if no callback is provided, or make it promisable (is that even a word 🥇 ?) with https://nodejs.org/api/util.html#util_custom_promisified_functions .
What we could do is make the formidable function return a promise if no callback is provided, or make it promisable (is that even a word ?) with https://nodejs.org/api/util.html#util_custom_promisified_functions .
What I was thinking for v3 is to focus just on the parser(s) and make helper tools and/or integrate them well with the Nodejs/Deno/Web built-ins, not that big monolith. That's basically what the examples are, at least at my point of view :D
it is confusing that the pipeline function
intentionally named it pipeline
to be similar to the stream.pipeline
, which is not confusing but quite the opposite to me - feels familiar and will expect to work similarly ;d
I published v2 and v3 on my npm account https://www.npmjs.com/package/@grossacasacs/formidable
I have some ideas also...
it's most likely that nodejs will ship the fetch api into core, so they have added support for things like Blobs into core to prep for the fetch api. now Blob
is accessible from require('node:buffer').Blob
They are also likely to ship File and FormData. (and some form of fetch-blob's fileFrom(path)
to fs
)
So how about turning things into more standardized & spec'ed api's?
eg: make use of FormData
and File
s (or Blob
)
Eventually we could just use node's built in fetch api and do:formData = await new Response(stream, { headers }).formData()
without any dependencies
fileFromSync(path. [mimetime])
and give this File to the developereventually you would just hand the developer a FormData instance of which they can do things like:
const file = formData.get('avatar')
const rawImageData = await file.arrayBuffer()
// and re-upload files or even the hole formData instance to AWS or some
// bucket using existing fetch api and not have to worry about having to include
// things like a multipart serialization package.
Well yes, I am still waiting for things to be native in Node.js, and I keep an eye open for those things. Blob for example is just experimental and formidable package should work on Node.js 14 still. Node fetch still not in Node.js core sadly
FormData package is for sending formdata, and formidable is for receiving. I am not sure if we can reuse some code. Or are you proposing to use FormData package to make it easy to forward a received form ? In that case feel free to add an example inside ./examples first.
Things I am thinking about
FormData package is for sending formdata, and formidable is for receiving. I am not sure if we can reuse some code. Or are you proposing to use FormData package to make it easy to forward a received form ? In that case feel free to add an example inside ./examples first.
FormData could be used for both sending and receiving, We wouldn't have Response.formData() or Request.formData() otherwise. They exist so you can intercept the payload from Service Worker. FormData did not have any method for reading/manipulating a FormData earlier but now we have .has(), .delete(), .get(), .entries(), for..of
and so on
So yes, I'm proposing to use a FormData package to make it easy to forward a received form. But also for parsing and consuming a received form
I think it would be cool if form.formData()
could utilize a FormData package and resolve the stream with a FormData instance. Here is an example from the Readme that i have rewritten:
import { createServer } from 'node:http'
import formidable from 'formidable'
const server = createServer(async (req, res) => {
if (req.url === '/api/upload' && req.method.toLowerCase() === 'post') {
// parse a file upload
const form = formidable({ uploadDir: '/tmp' });
// Similar to whatwg `request.fromData()`
const formData = await form.formData(req)
console.log(...formData.entries())
// file is a web File object
const file = formData.get('file') // ( The file could be backed up by the filesystem - in /tmp dir )
console.log(file.name)
res.writeHead(200, {
'Content-Type': file.type,
'Content-Length': file.size
})
// file.stream() is a whatwg:stream
for await (const uint8 of file.stream()) {
res.send(uint8)
}
return res.end()
}
// show a file upload form
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(`
<h2>With Node.js <code>"http"</code> module</h2>
<form action="/api/upload" enctype="multipart/form-data" method="post">
<div>Text field title: <input type="text" name="title" /></div>
<div>File: <input type="file" name="file" /></div>
<input type="submit" value="Upload" />
</form>
`)
})
server.listen(8080, () => {
console.log('Server listening on http://localhost:8080/ ...')
});
(calling form.formData()
would work for both urlencoded and multipart payloads)
On a side note: you said you wanted to handle a (web)stream... that would be 👍,
it would be great if formidable was not so bound to HttpIncomingRequest so you can just hand it some iterable object (wheater it be stream, req, webstream or any asyncIterator) - so something like:
const formData = await form.formData(iterable, contentType)
maybe? idk
I see it as breaking changes (fields and files output changen new dependency) so we can start with version 4.x I add you to formdiable github so you can create new branches and make commits if you want to try to implement your ideas in a new branch, (use 3.x as a starting point)
Overall I like the ideas to move towards web apis when possible.
Hmm, okey - i can give it a go. One thing doe... my FormData package is ESM-only, so is fetch-blob
which it also depends on.
├─┬ formdata-polyfill (esm-only)
│ └─┬ fetch-blob (esm-only)
│ └── web-streams-polyfill
b/c .formData()
would be async then it would be possible to lazy load ESM-only packages using the async import('formdata-polyfill')
from cjs. So it would be no biggie if you would like to keep it as commonjs. But i was just wondering if you wish to make the switch to ESM-only as well...? since it is going to be a major change anyway...
(Also formdata-polyfill + fetch-blob both depend on node v12.20+ stuff) including them would mean you would have to target same node versions
3.x is already ESM only
Think there are som bugs in 3.x that needs to be addressed first... the test didn't run so well...
One day I tried to make all the test pass but I didn't finish, I 'll have a look this evening again
When do we publish v3 as latest ?
I suggest somewhere in December or January. Lets give some time to see how it goes.
(i will edit the things now as to #769, what to finish it as quick as possible cuz a lot of people we see PRs so it should be clear)
@Songkeys @jimmywarting check #769 and the comments from today in #718.
I'm back, i guess. Interested in crypto more and more so i need dev environment and will be on github more. I seen they launched Codespaces publicly, I should try it tho.
Crypto Hodl gang ! Codespace is visual studio in the cloud, pretty cool I used something similar hacked together in 2015, neat for multi user collaboration
Haha, totally... for both.
the devDep seems a bit outdated, update them?
Somebody should do it ...
@jimmywarting meh, they are okay, especially as I didn't look on latest updates on Prettier and Airbnb, just check ESLint 7 before some weeks/months. Jest is usually not that breaking too so.
Read this https://www.theregister.com/2018/11/26/npm_repo_bitcoin_stealer/
For me the biggest question is not if it is compatible, but if it looks good (including sub dependencies) I try to read the npm diff with npmfs but it takes a lot of time
@GrosSacASac that's old, is there some recent things like that? But yea, got it, we should stay updated, true.
There's dependabot PRs, but the automerge isn't working for some reason or isn't enabled. That would be the best.
I KNow it is old but it could happen again,
I like the FormData and Blob ideas.
But whatever we do, we really need to make benchmarks first. Everything boils down to that, otherwise there's no sense in Formidable. Why it exists in the first place. It used to be one of the fastest streaming multipart parsers, and that is at its core.
With all the recent Nodejs versions, updates, new standards and all, the question comes more and more. And is it fast at all, with the new Streams APIs. Working in blind isn't good.
From what I understand, and from @jimmywarting's examples, I think we should make the following:
Initialize FormData
above that, in the Multipart plugin
https://github.com/node-formidable/formidable/blob/81dd350835e14dccca667fc46bc5c35f16f1b5ec/src/plugins/multipart.js#L51
and on parser.on('data')
just fd.append
the incoming file. Getting the things from part
and put them on append('file', new File(part.name, { type: part.mimetype, ... }))
Right? Then just wrap that and expose formData()
method to the user so it will get the fd
. From there our job ends: we parsed the request and give you standard FormData.
And can just provide extensions for further common things.
or
fd.append('file', {
size: part.size,
type: part.mimetype,
name: part.name,
stream() { return part },
[Symbol.toStringTag]: 'File'
})
something like this
const form = formidable({ uploadDir: "./uploads" });
const formData = await form.parse(req);
// extension/utility that write files to disk,
for (const [, file] of formData.entries()) {
file.stream().pipe(fs.createWriteStream(file.name));
}
quite not right to me tho, but that's userland
@jimmywarting ha, didn't knew that node-fetch
is using formidable's multipart parser, hehe.
The only difference is that it's not Transform stream and uses uint8array. Seems like it also expose toFormData
which is exactly what I thought in my previous comment LOL - thin layer on top of the parser.
Do you mind to extract it as @formidable/multipart
and we will both use it? Not sure if the namespace is free.
@jimmywarting 👆👆
pipeline(...streams, (err) => {})
multipart/*
?Readable.from(multipart())
?VFile
orVinyl
? v2?Okay, some thoughts.
I think we can just wrap the Node's
stream.pipeline()
and provide it to our users, but they still will be able to do whatever they want, using the Node'spipeline()
. Something like this