Permalink
Browse files

added bodyEncoder uploading multiple files

  • Loading branch information...
coolaj86 committed Jan 13, 2011
1 parent 58df363 commit 455a0ad115c10fa2cd25ef0ae0a9d2433b50cb31
@@ -0,0 +1,60 @@
+{
+ fields: {
+ "name": "ONeal",
+ "favs[]": "Wednesday",
+ "email": "coolaj86@gmail.com"
+ },
+ files: {
+ "avatar": {
+ "length": 869,
+ "path": "/tmp/25832c732b5a165954ba3801a85c3a7c.png",
+ "filename": "smiley-cool.png",
+ "mime": "image/png",
+ "_writeStream": {
+ "path": "/tmp/25832c732b5a165954ba3801a85c3a7c.png",
+ "fd": 9,
+ "writeable": false,
+ "flags": "w",
+ "encoding": "binary",
+ "mode": 438,
+ "busy": false,
+ "_queue": [],
+ "drainable": true
+ }
+ },
+ "files": {
+ "length": 58,
+ "path": "/tmp/7bce8d3c3cb036c0748acfd9be34f1f8.txt",
+ "filename": "file2.txt",
+ "mime": "text/plain",
+ "_writeStream": {
+ "path": "/tmp/7bce8d3c3cb036c0748acfd9be34f1f8.txt",
+ "fd": 11,
+ "writeable": false,
+ "flags": "w",
+ "encoding": "binary",
+ "mode": 438,
+ "busy": false,
+ "_queue": [],
+ "drainable": true
+ }
+ },
+ "attachments[]": {
+ "length": 58,
+ "path": "/tmp/df733d4f2571e936c68f3532ec45f730.txt",
+ "filename": "file2.txt",
+ "mime": "text/plain",
+ "_writeStream": {
+ "path": "/tmp/df733d4f2571e936c68f3532ec45f730.txt",
+ "fd": 13,
+ "writeable": false,
+ "flags": "w",
+ "encoding": "binary",
+ "mode": 438,
+ "busy": false,
+ "_queue": [],
+ "drainable": true
+ }
+ }
+ }
+}
View
@@ -2,68 +2,73 @@
"use strict";
var http = require('http'),
+ FileApi = require('../lib'),
File = require('../lib/file.js'),
FormData = require('../lib/form-data.js'),
formData = new FormData(),
- chunked = (new Date().valueOf() % 2) ? true : false,
client = http.createClient(3000, 'localhost'),
+ headers = {
+ "Host": "localhost:3000",
+ "User-Agent": "Node.js (AbstractHttpRequest)",
+ "Accept-Encoding": "gzip,deflate",
+ //"Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7",
+ //"Keep-Alive": 115,
+ //"Connection": "keep-alive",
+ },
+ //chunked = false,
+ chunked = true,
bodyStream,
- headers,
- body,
- request,
- requestChunked;
+ request;
- formData.setNodeChunkedEncoding(chunked);
- formData.append('items[]', 'value0');
- formData.append('items[]', 'value1');
- formData.append('item', 'value2');
- formData.append('item', 'value3');
- formData.append('file1', new File(__dirname + '/../files/file2.txt'));
- formData.append('emoticon', new File(__dirname + '/../files/smiley-cool.png'));
- formData.append('avatar', new File(__dirname + '/../files/coolaj86-2010.jpg'));
+ function encodeBody() {
+ formData.setNodeChunkedEncoding(chunked);
+ formData.append('nam0', 'AJ');
+ formData.append('name', 'ONeal');
+ formData.append('favs0]', 'Blue');
+ formData.append('favs[]', 'Wednesday');
+ formData.append('email', 'coolaj86@gmail.com');
+ formData.append('avatar', new File(__dirname + '/../files/smiley-cool.png'));
+ formData.append('file0', new File(__dirname + '/../files/file1.txt'));
+ formData.append('files', new File(__dirname + '/../files/file2.txt'));
+ formData.append('attachments0]', new File(__dirname + '/../files/file1.txt'));
+ formData.append('attachments[]', new File(__dirname + '/../files/file2.txt'));
+ }
- // Uses 'x-www-form-urlencoded' if possible
- bodyStream = formData.serialize('x-www-form-urlencoded');
+ function sendBody() {
+ // Uses 'x-www-form-urlencoded' if possible, but falls back to 'multipart/form-data; boundary=`randomString()`'
+ bodyStream = formData.serialize('x-www-form-urlencoded');
- requestChunked = client.request('POST', '/', {
- "Host": "localhost:3000",
- "Content-Type": formData.getContentType(),
- "Transfer-Encoding": "chunked"
- });
+ headers["Content-Type"] = formData.getContentType();
+ if (chunked) {
+ request = client.request('POST', '/', headers);
+ bodyStream.on('data', function (data) {
+ request.write(data);
+ });
+ }
- bodyStream.on('data', function (data) {
- // Node takes care of the chunk sizes and count
- /*
- "\r\n" + data.length.toString(16) + data
- */
- requestChunked.write(data);
- });
- bodyStream.on('load', function () {
- // Node takes care of the body footer
- /*
- "\r\n" + 0 + "\r\n\r\n"
- */
- requestChunked.end();
- });
-
- // `size` will always occur before `load`
- // but will likely not occur before the
- // first `data`
- bodyStream.on('size', function (size) {
- request = client.request('POST', '/', {
- "Host": "localhost",
- "Content-Type": formData.getContentType(),
- "Content-Length": size
+ // `data` will usually fire first, then `size`, then more `data`, then `load`
+ bodyStream.on('size', function (size) {
+ if (chunked) {
+ return;
+ } else {
+ headers["Content-Length"] = size;
+ request = client.request('POST', '/', headers);
+ }
});
- });
- bodyStream.on('load', function (data) {
- request.write(data);
- request.end();
- request.on('response', function (response) {
- //response.on();
+ bodyStream.on('load', function (data) {
+ if (!chunked) {
+ request.write(data);
+ }
+ request.end();
});
- });
+ }
+
+ encodeBody();
+ sendBody();
+ // Does work on keep-alive
+ // Does work with content-length
+ // Does work when chunked (when content-length is commented out)
}());
Binary file not shown.
Binary file not shown.
View
@@ -0,0 +1,62 @@
+/**
+ * Module dependencies.
+ */
+
+var express = require('express'),
+ form = require('connect-form'),
+ sys = require('sys');
+
+var app = express.createServer(
+ // connect-form (http://github.com/visionmedia/connect-form)
+ // middleware uses the formidable middleware to parse urlencoded
+ // and multipart form data
+ form({ keepExtensions: true })
+);
+
+
+app.get('/', function(req, res){
+ res.send('<html><body>\n'
+ + '\t<form method="post" enctype="multipart/form-data">\n'
+ + '\t\t<p>First: <input type="text" name="name" /></p>\n'
+ + '\t\t<p>Last: <input type="text" name="name" /></p>\n'
+ + '\t\t<p>Favorite Color: <input type="text" name="favs[]" /></p>\n'
+ + '\t\t<p>Favorite Weekday: <input type="text" name="favs[]" /></p>\n'
+ + '\t\t<p>Email: <input type="text" name="email" /></p>\n'
+ + '\t\t<p>Avatar: <input type="file" name="avatar" /></p>\n'
+ + '\t\t<p>Files: <input type="file" name="files" multiple=multiple /></p>\n'
+ + '\t\t<p>Attachments[]: <input type="file" name="attachments[]" multiple=multiple /></p>\n'
+ + '\t\t<p><input type="submit" value="Upload" /></p>\n'
+ + '\t</form>\n'
+ + '</body></html>\n');
+});
+
+app.post('/', function(req, res, next){
+
+ // connect-form adds the req.form object
+ // we can (optionally) define onComplete, passing
+ // the exception (if any) fields parsed, and files parsed
+ req.form.complete(function(err, fields, files){
+ if (err) {
+ next(err);
+ } else {
+ console.log(JSON.stringify({
+ fields: fields,
+ files: files
+ }, null, ' '));
+ //console.log('\nuploaded %s to %s',
+ //files.image.filename,
+ //files.image.path);
+ res.redirect('back');
+ }
+ });
+
+ // We can add listeners for several form
+ // events such as "progress"
+ req.form.addListener('progress', function(bytesReceived, bytesExpected){
+ var percent = (bytesReceived / bytesExpected * 100) | 0;
+ sys.print('Uploading: %' + percent + '\r');
+ });
+});
+
+app.listen(3000);
+console.log('Express app started on port 3000');
View
@@ -57,12 +57,18 @@
*/
}
+ var first = true;
function toContentDisposition(key, val) {
var emitter = new EventEmitter(),
text = '',
fr;
- text += '\r\n--' + self.boundary;
+ if (first) {
+ first = false;
+ } else {
+ text += '\r\n';
+ }
+ text += '--' + self.boundary;
text += "\r\nContent-Disposition: form-data; name=" + key.quote();
if (!isFile(val)) {
@@ -76,6 +82,7 @@
} else {
fr = new FileReader();
fr.on('loadstart', function () {
+ text += '; filename="' + val.name + '"';
text += "\r\nContent-Type: " + (val.type || 'application/binary') + "\r\n\r\n";
emitter.emit('data', new Buffer(text));
});
@@ -110,9 +117,6 @@
var emitter = new EventEmitter(),
buffers = [];
- // TODO randomize
- self.boundary = 'abcdefghiponmlkjqrstzyxwvu';
-
emitter.on('data', function (data) {
buffers.push(data);
});
@@ -128,6 +132,8 @@
.then(next);
})
.then(function (next) {
+ var footer = new Buffer("\r\n--" + self.boundary + "--\r\n");
+ emitter.emit('data', footer);
emitter.emit('ready');
next(); // does cleanup
});
@@ -154,6 +160,36 @@
function toFormUrlEncoded() {
}
+ function randomString(len, charset) {
+ var numbers = "0123456789",
+ ualpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
+ lalpha = "abcdefghijklmnopqrstuvwxyz",
+ special = "+/-_:",
+ radix = {
+ base16: numbers + ualpha,
+ base36: numbers + ualpha,
+ base64: ualpha + lalpha + numbers + special.substr(0,2),
+ base64url: ualpha + lalpha + numbers + special.substr(2,2),
+ base64xml: ualpha + lalpha + numbers + special.substr(3,2)
+ },
+ result = '',
+ chars,
+ length,
+ seed,
+ i;
+
+ length = len || 8;
+ chars = radix[charset] || charset || radix['base64url'];
+
+ for (i = 0; i < length; i +=1) {
+ seed = Math.floor(Math.random() * chars.length);
+ result += chars.substring(seed, seed + 1);
+ }
+
+ return result;
+ }
+
+
self.serialize = function (intendedType) {
self.type = intendedType = (intendedType || '').toLowerCase();
@@ -171,6 +207,14 @@
console.log("ContentType changed `multipart/form-data`: Some of the upload items are `HTML5::FileAPI::File`s.");
}
+
+ // This is how FireFox does it. Seems good enough to me.
+ // Note that the spec also allows a space in the middle, but not at the end
+ // http://www.w3.org/Protocols/rfc1341/7_2_Multipart.html
+ //self.boundary = "---------------------------5414130496409022042012852923";
+ self.boundary = '---------------------------' + randomString(28, 'base64url', "'()+_,-./:=?");
+ self.type += '; boundary=' + self.boundary;
+
return toFormData();
}
View
@@ -4,13 +4,35 @@
(function () {
"use strict";
- module.exports = {
+ var FileApi = module.exports = {
File: require('./file'),
FileList: require('./file-list'),
//FileError: require('./file-error'),
FileReader: require('./file-reader'),
FormData: require('./form-data')
- }
+ };
+
+ FileApi.isFile = function (obj) {
+ if (obj instanceof FileApi.File) {
+ return true;
+ }
+ if ('string' === typeof obj.name
+ && (obj.path || obj.stream || obj.buffer)) {
+ return true;
+ }
+ return false;
+ };
+
+ FileApi.isFormData = function (obj) {
+ if (obj instanceof FileApi.FormData) {
+ return true;
+ }
+ if ('function' === typeof obj.append
+ && 'function' === typeof obj.serialize) {
+ return true;
+ }
+ return false;
+ };
if ('undefined' === typeof provide) { provide = function() {}; }
provide('file-api');

0 comments on commit 455a0ad

Please sign in to comment.