poor-form
v1.1.4
Published
Built upon formaline's ultrafast parser, truly a formidable competitor.
Downloads
37
Maintainers
Readme
PoorForm
PoorForm uses formaline's ultra-fast parser (QAP) to create a much simpler multi-part form parser.
It may be insignificantly faster than both formidable and formaline, but that's not the point.
The point is that it's a simple base to build upon, kitchen sink not included.
Truly a formidable competitor.
npm install poor-form
Test
There are two tests.
The first walks a directory and checks the md5 sums of the files against the md5sums calculated by the server.
The second creates a few thousand form submissions where each form has one more byte than the previous form (an attempt to catch off-by-one errors).
git clone git://github.com/coolaj86/poor-form.git
cd poor-form
cp npm-shrinkwrap.bak.json npm-shrinkwrap.json
npm install --dev
node test/example-md5sum-service.js 4444 &
node test/md5sum-test.js .
node test/md5sum-bits-test.js
If you encounter any errors running the test, it's probably just an issue of dependencies
(there's some instanceof
magic that fails if any modules from file-api
are installed twice),
but the npmshrinkwrap.json
should be preventing this.
API
PoorForm.create(req)
PoorForm#on('fieldstart', fn)
PoorForm#on('fielddata', fn)
PoorForm#on('fieldend', fn)
PoorForm#on('formend', fn)
PoorForm#total
PoorForm#loaded
PoorForm.create(request)
Returns a PoorForm
emitter instance if !req.complete
and /multipart/.test(req.headers['content-type'])
.
Returns null
otherwise - either it's not a multi-part form, or the form has already been parsed.
Example
// Using Connect, for example
app.use(function (req, res, next) {
var poorForm = PoorForm.create(req)
, fields = []
, count = 0
, curField
;
if (!poorForm) {
console.log("Either this was already parsed or it isn't a multi-part form");
next();
return;
}
// poorForm.on('fieldstart', ...)
// ...
});
PoorForm#on('fieldstart', function (headers) { ... })
Emitted each time a new field is encountered.
headers
will contain all raw mime headers (with lower-cased keys) as well as a few shortcut keys
headers = {
name: "foo-fieldname" // parsed value from Content-Disposition
, filename: "big.bin" // parsed value from Content-Disposition
, type: "application/json" // Just the MIME-type of the Content-Type
, 'content-type': "application/json; charset=utf-8"
, 'content-disposition': 'form-data; name="foo-fieldname"; filename="big.bin"'
, ... // any other raw headers (usually none)
}
Example
poorForm.on('fieldstart', function (headers) {
var tmpPath = '/tmp/upload-' + count + '.bin'
;
count += 1;
curField = {};
if (headers.filename) {
console.log('Probably a file and probably has a mime-type', headers.type);
curField.fw = fs.createWriteStream(tmpPath);
curField.tmpPath = tmpPath;
} else {
console.log('Probably a field without a mime-type', headers.type);
curField.value = '';
}
curField.totalBytes = 0;
curField.headers = headers;
});
PoorForm#on('fielddata', function (buffer) { ... })
Emitted for each chunk of data that belongs to a field or file (no headers, whitespace, etc).
poorForm.on('fielddata', function (buffer) {
if (curField.fw) {
curField.fw.write(buffer);
console.log('Just wrote', buffer.length, 'bytes of a file');
} else {
curField.value += buffer.toString('utf8');
}
curField.totalBytes += buffer.length;
});
NOTE: It's very possible for a single field with very few bytes to come in with multiple chunks
PoorForm#on('fieldend', function () { ... })
Emitted when the current field or file has completed.
poorForm.on('fieldend', function () {
var lastField = curField
;
if (curField.fw) {
curField.fw.end();
curField.fw = undefined;
console.log('Just wrote a file of ', curField.totalBytes, 'bytes');
fs.rename(curField.tmpPath, '/tmp/' + curField.headers.filename, function () {
console.log('Renamed', lastField.tmpPath, 'to', lastField.headers.filename);
});
} else {
console.log('Just received', curField.headers.name + ':' + curField.value);
}
fields.push(curField);
curField = null;
});
PoorForm#on('formend', function () { ... })
Emitted when the end-of-form boundary has been encountered.
poorForm.on('formend', function () {
res.end(JSON.stringify(fields, null, ' '));
});
PoorForm#loaded
Number of bytes received so far - including all headers, whitespace, form fields, and files.
req.on('data', function () {
var ratio = poorForm.loaded / poorForm.total
, percent = Math.round(ratio * 100)
;
console.log(percent + '% complete (' + poorForm.loaded + ' bytes)');
// might be 0, if poorForm.total is Infinity
});
PoorForm#total
The total number of bytes in the form - the same as req.headers['content-length']
.
console.log(poorForm.total + 'bytes received thus far');
NOTE: If the content encoding is chunked
poorForm.total will be Infinity
.
Example: An md5sum webservice
/*jshint strict:true node:true es5:true onevar:true laxcomma:true laxbreak:true eqeqeq:true immed:true latedef:true unused:true undef:true*/
(function () {
"use strict";
var connect = require('connect')
, PoorForm = require('poor-form')
, crypto = require('crypto')
, port = process.argv[2] || 3000
, server
, app
;
// An md5sum service
app = connect.createServer()
.use(function (req, res, next) {
var poorForm = PoorForm.create(req)
, hash
, info
, hashes = []
;
if (!poorForm) {
console.log("Either this was already parsed or it isn't a multi-part form");
next();
return;
}
poorForm.on('fieldstart', function (headers) {
console.log('[fieldstart]', headers.filename || headers.name);
hash = crypto.createHash('md5');
info = headers;
});
poorForm.on('fielddata', function (chunk) {
hash.update(chunk);
});
poorForm.on('fieldend', function () {
info.md5sum = hash.digest('hex');
console.log(info.md5sum);
hashes.push(info);
});
poorForm.on('formend', function () {
console.log('[formend]');
res.end(JSON.stringify({ "success": true, "result": hashes }));
});
})
;
server = app.listen(port, function () {
console.log(server.address());
});
}());
Possible Future Enhancements
There are a few derivations of the multipart/*
type:
multipart/form-data
(with dispositionsform-data
andfile
) W3 Specmultipart/mixed
(similar to thefile
disposition, but without a declared disposition orfilename
) W3 RFC 1341multipart/alternative
multipart/digest
multipart/parallel
poor-form
could very easily be adapted to handle these types as well.
However, I don't know of any practical use for them at the moment.
Bugs
If a form ends unexpectedly, curFile
should be closed.
Needs an error for when a form writes past the end boundary
Needs a default size limit on headers (4k would be more than reasonable) before sending an error