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: use modern Streams API #531

Merged
merged 30 commits into from
Jan 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
7eeb7d9
start with streams
GrosSacASac Dec 4, 2019
8634386
fix JSON decoder
GrosSacASac Dec 4, 2019
9010a17
save
GrosSacASac Dec 5, 2019
868a4e3
Merge branch 'master' into streams
GrosSacASac Dec 6, 2019
daebbde
Update multipart_parser.js
GrosSacASac Dec 9, 2019
2f9191d
use streams API in the multipart example
GrosSacASac Dec 17, 2019
214708a
use sreaing api
GrosSacASac Dec 17, 2019
b688b66
use the new multipartparser which is a stream
GrosSacASac Dec 17, 2019
308dfa0
revert, add todo
GrosSacASac Dec 17, 2019
a3fe96b
emit error via recommended stream way
GrosSacASac Dec 18, 2019
63ec105
octet parser is passtrough
GrosSacASac Dec 18, 2019
c4f8d7b
querystringparser is a stream
GrosSacASac Dec 18, 2019
dd4a9d5
Update incoming_form.js
GrosSacASac Dec 18, 2019
bf10f2f
Dummy parser is a stream
GrosSacASac Dec 21, 2019
e9f226d
formatting
GrosSacASac Dec 22, 2019
3f392e1
listen for errors earlier
GrosSacASac Dec 26, 2019
40b830e
decouple, don't self check internals
GrosSacASac Dec 26, 2019
ee61052
use existing var
GrosSacASac Dec 26, 2019
e2f9d08
display name of failing test
GrosSacASac Dec 26, 2019
167321d
Merge branch 'master' into streams
GrosSacASac Jan 8, 2020
a548c6b
chore: tweaks
tunnckoCore Jan 28, 2020
5f9a297
chore: merge with latest master
tunnckoCore Jan 28, 2020
2d49302
Merge branch 'master' into streams
tunnckoCore Jan 28, 2020
6524105
chore: switch lib/ to src/ in tests
tunnckoCore Jan 28, 2020
9d9f212
chore: omg merge branch 'streams' of github.com:node-formidable/node-…
tunnckoCore Jan 28, 2020
3d0b96d
chore: comment failing test, fix ti soon
tunnckoCore Jan 28, 2020
0bd03a1
chore: make tests passing; ignore workarounds fixture
tunnckoCore Jan 28, 2020
8c168e6
chore: proper ignore; use tap reporter;
tunnckoCore Jan 28, 2020
1bef152
chore: some formatting happens...
tunnckoCore Jan 28, 2020
c1911af
chore: add to changelog
tunnckoCore Jan 28, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
* Improve examples and tests ([#523](https://github.com/node-formidable/node-formidable/pull/523))
* First step of Code quality improvements ([#525](https://github.com/node-formidable/node-formidable/pull/525))
* chore(funding): remove patreon & add npm funding field ([#525](https://github.com/node-formidable/node-formidable/pull/532)
* Modern Streams API ([#531](https://github.com/node-formidable/node-formidable/pull/531))

### v1.2.1 (2018-03-20)

Expand Down
2 changes: 1 addition & 1 deletion example/json.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ server = http.createServer(function(req, res) {
form
.on('error', function(err) {
res.writeHead(500, {'content-type': 'text/plain'});
res.end('error:\n\n'+util.inspect(err));
res.end('error:\n\n' + util.inspect(err));
console.error(err);
})
.on('field', function(field, value) {
Expand Down
34 changes: 13 additions & 21 deletions example/multipartParser.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
const { MultipartParser } = require('../lib/multipart_parser.js');


const multipartParser = new MultipartParser();

// hand crafted multipart
const boundary = '--abcxyz';
const next = '\r\n';
Expand All @@ -11,25 +9,19 @@ const buffer = Buffer.from(
`${boundary}${next}${formData}name="text"${next}${next}text ...${next}${next}${boundary}${next}${formData}name="z"${next}${next}text inside z${next}${next}${boundary}${next}${formData}name="file1"; filename="a.txt"${next}Content-Type: text/plain${next}${next}Content of a.txt.${next}${next}${boundary}${next}${formData}name="file2"; filename="a.html"${next}Content-Type: text/html${next}${next}<!DOCTYPE html><title>Content of a.html.</title>${next}${next}${boundary}--`
);

const logAnalyzed = (buffer, start, end) => {
const multipartParser = new MultipartParser();
multipartParser.on('data', ({name, buffer, start, end}) => {
console.log(`${name}:`);
if (buffer && start && end) {
console.log(String(buffer.slice(start, end)))
console.log(String(buffer.slice(start, end)));
}
};

// multipartParser.onPartBegin
// multipartParser.onPartEnd

// multipartParser.on('partData', logAnalyzed) // non supported syntax
multipartParser.onPartData = logAnalyzed;
multipartParser.onHeaderField = logAnalyzed;
multipartParser.onHeaderValue = logAnalyzed;
multipartParser.initWithBoundary(boundary.substring(2));


const bytesParsed = multipartParser.write(buffer);
const error = multipartParser.end();

if (error) {
console.log();
});
multipartParser.on('error', (error) => {
console.error(error);
}
});

multipartParser.initWithBoundary(boundary.substring(2)); // todo make better error message when it is forgotten
// const shouldWait = !multipartParser.write(buffer);
multipartParser.end();
// multipartParser.destroy();
2 changes: 1 addition & 1 deletion src/default_options.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ const defaultOptions = {
hash: false,
multiples: false,
};

exports.defaultOptions = defaultOptions;
18 changes: 18 additions & 0 deletions src/dummy_parser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const { Transform } = require('stream');


class DummyParser extends Transform {
constructor(incomingForm) {
super();
this.incomingForm = incomingForm;
}

_flush(callback) {
this.incomingForm.ended = true;
this.incomingForm._maybeEnd();
callback();
}
}


exports.DummyParser = DummyParser;
2 changes: 1 addition & 1 deletion src/file.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ function File(properties) {
this.lastModifiedDate = null;

this._writeStream = null;

for (var key in properties) {
this[key] = properties[key];
}
Expand Down
229 changes: 113 additions & 116 deletions src/incoming_form.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ var util = require('util'),
path = require('path'),
File = require('./file'),
defaultOptions = require('./default_options').defaultOptions,
DummyParser = require('./dummy_parser').DummyParser,
MultipartParser = require('./multipart_parser').MultipartParser,
QuerystringParser = require('./querystring_parser').QuerystringParser,
OctetParser = require('./octet_parser').OctetParser,
Expand Down Expand Up @@ -140,10 +141,7 @@ IncomingForm.prototype.parse = function(req, cb) {
return;
}

var err = this._parser.end();
if (err) {
this._error(err);
}
this._parser.end();
});

return this;
Expand All @@ -153,6 +151,9 @@ IncomingForm.prototype.writeHeaders = function(headers) {
this.headers = headers;
this._parseContentLength();
this._parseContentType();
this._parser.once('error', (error) => {
this._error(error);
});
};

IncomingForm.prototype.write = function(buffer) {
Expand All @@ -167,12 +168,9 @@ IncomingForm.prototype.write = function(buffer) {
this.bytesReceived += buffer.length;
this.emit('progress', this.bytesReceived, this.bytesExpected);

var bytesParsed = this._parser.write(buffer);
if (bytesParsed !== buffer.length) {
this._error(new Error(`parser error,${bytesParsed} of ${buffer.length} bytes parsed`));
}
this._parser.write(buffer);

return bytesParsed;
return this.bytesReceived;
};

IncomingForm.prototype.pause = function() {
Expand Down Expand Up @@ -249,19 +247,10 @@ IncomingForm.prototype.handlePart = function(part) {
});
};

function dummyParser(incomingForm) {
return {
end: function () {
incomingForm.ended = true;
incomingForm._maybeEnd();
return null;
}
};
}

IncomingForm.prototype._parseContentType = function() {
if (this.bytesExpected === 0) {
this._parser = dummyParser(this);
this._parser = new DummyParser(this);
return;
}

Expand Down Expand Up @@ -341,98 +330,103 @@ IncomingForm.prototype._initMultipart = function(boundary) {

parser.initWithBoundary(boundary);

parser.onPartBegin = function() {
part = new Stream();
part.readable = true;
part.headers = {};
part.name = null;
part.filename = null;
part.mime = null;

part.transferEncoding = 'binary';
part.transferBuffer = '';

headerField = '';
headerValue = '';
};

parser.onHeaderField = (b, start, end) => {
headerField += b.toString(this.encoding, start, end);
};

parser.onHeaderValue = (b, start, end) => {
headerValue += b.toString(this.encoding, start, end);
};

parser.onHeaderEnd = () => {
headerField = headerField.toLowerCase();
part.headers[headerField] = headerValue;

// matches either a quoted-string or a token (RFC 2616 section 19.5.1)
var m = headerValue.match(/\bname=("([^"]*)"|([^\(\)<>@,;:\\"\/\[\]\?=\{\}\s\t/]+))/i);
if (headerField == 'content-disposition') {
if (m) {
part.name = m[2] || m[3] || '';
}
parser.on('data', ({name, buffer, start, end}) => {
if (name === 'partBegin') {
part = new Stream();
part.readable = true;
part.headers = {};
part.name = null;
part.filename = null;
part.mime = null;

part.transferEncoding = 'binary';
part.transferBuffer = '';

headerField = '';
headerValue = '';
} else if (name === 'headerField') {
headerField += buffer.toString(this.encoding, start, end);
} else if (name === 'headerValue') {
headerValue += buffer.toString(this.encoding, start, end);
} else if (name === 'headerEnd') {
headerField = headerField.toLowerCase();
part.headers[headerField] = headerValue;

// matches either a quoted-string or a token (RFC 2616 section 19.5.1)
var m = headerValue.match(/\bname=("([^"]*)"|([^\(\)<>@,;:\\"\/\[\]\?=\{\}\s\t/]+))/i);
if (headerField == 'content-disposition') {
if (m) {
part.name = m[2] || m[3] || '';
}

part.filename = this._fileName(headerValue);
} else if (headerField == 'content-type') {
part.mime = headerValue;
} else if (headerField == 'content-transfer-encoding') {
part.transferEncoding = headerValue.toLowerCase();
}
part.filename = this._fileName(headerValue);
} else if (headerField == 'content-type') {
part.mime = headerValue;
} else if (headerField == 'content-transfer-encoding') {
part.transferEncoding = headerValue.toLowerCase();
}

headerField = '';
headerValue = '';
};
headerField = '';
headerValue = '';
} else if (name === 'headersEnd') {

switch(part.transferEncoding){
case 'binary':
case '7bit':
case '8bit': {
const dataPropagation = ({name, buffer, start, end}) => {
if (name === 'partData') {
part.emit('data', buffer.slice(start, end));
}
};
const dataStopPropagation = ({name}) => {
if (name === 'partEnd') {
part.emit('end');
parser.off('data', dataPropagation);
parser.off('data', dataStopPropagation);
}
};
parser.on('data', dataPropagation);
parser.on('data', dataStopPropagation);
break;
} case 'base64': {
const dataPropagation = ({name, buffer, start, end}) => {
if (name === 'partData') {
part.transferBuffer += buffer.slice(start, end).toString('ascii');

/*
four bytes (chars) in base64 converts to three bytes in binary
encoding. So we should always work with a number of bytes that
can be divided by 4, it will result in a number of buytes that
can be divided vy 3.
*/
var offset = parseInt(part.transferBuffer.length / 4, 10) * 4;
part.emit('data', Buffer.from(part.transferBuffer.substring(0, offset), 'base64'));
part.transferBuffer = part.transferBuffer.substring(offset);
}
};
const dataStopPropagation = ({name}) => {
if (name === 'partEnd') {
part.emit('data', Buffer.from(part.transferBuffer, 'base64'));
part.emit('end');
parser.off('data', dataPropagation);
parser.off('data', dataStopPropagation);
}
};
parser.on('data', dataPropagation);
parser.on('data', dataStopPropagation);
break;

} default:
return this._error(new Error('unknown transfer-encoding'));
}

parser.onHeadersEnd = () => {
switch(part.transferEncoding){
case 'binary':
case '7bit':
case '8bit':
parser.onPartData = function(b, start, end) {
part.emit('data', b.slice(start, end));
};

parser.onPartEnd = function() {
part.emit('end');
};
break;

case 'base64':
parser.onPartData = function(b, start, end) {
part.transferBuffer += b.slice(start, end).toString('ascii');

/*
four bytes (chars) in base64 converts to three bytes in binary
encoding. So we should always work with a number of bytes that
can be divided by 4, it will result in a number of buytes that
can be divided vy 3.
*/
var offset = parseInt(part.transferBuffer.length / 4, 10) * 4;
part.emit('data', Buffer.from(part.transferBuffer.substring(0, offset), 'base64'));
part.transferBuffer = part.transferBuffer.substring(offset);
};

parser.onPartEnd = function() {
part.emit('data', Buffer.from(part.transferBuffer, 'base64'));
part.emit('end');
};
break;

default:
return this._error(new Error('unknown transfer-encoding'));
this.onPart(part);
} else if (name === 'end') {
this.ended = true;
this._maybeEnd();
}

this.onPart(part);
};


parser.onEnd = () => {
this.ended = true;
this._maybeEnd();
};
});

this._parser = parser;
};
Expand All @@ -456,9 +450,9 @@ IncomingForm.prototype._initUrlencoded = function() {

var parser = new QuerystringParser(this.maxFields);

parser.onField = (key, val) => {
this.emit('field', key, val);
};
parser.on('data', ({key, value}) => {
this.emit('field', key, value);
});

parser.onEnd = () => {
this.ended = true;
Expand Down Expand Up @@ -525,16 +519,19 @@ IncomingForm.prototype._initOctetStream = function() {
IncomingForm.prototype._initJSONencoded = function() {
this.type = 'json';

var parser = new JSONParser(this);
var parser = new JSONParser();

parser.onField = (key, val) => {
this.emit('field', key, val);
};
parser.on('data', ({ key, value }) => {
this.emit('field', key, value);
});
// parser.on('data', (key) => {
// this.emit('field', key);
// });

parser.onEnd = () => {
parser.once('end', () => {
this.ended = true;
this._maybeEnd();
};
});

this._parser = parser;
};
Expand Down