Skip to content

Commit

Permalink
support multi streams.
Browse files Browse the repository at this point in the history
  • Loading branch information
fengmk2 committed Oct 11, 2012
1 parent fc8fd67 commit 5a430fb
Show file tree
Hide file tree
Showing 8 changed files with 298 additions and 59 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
lib-cov
coverage.html
*.seed
*.log
*.csv
Expand All @@ -12,4 +13,4 @@ logs
results

node_modules
npm-debug.log
npm-debug.log
1 change: 0 additions & 1 deletion .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,3 @@ lib-cov/
Makefile
.travis.yml
logo.png
example/
22 changes: 15 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ formstream [![Build Status](https://secure.travis-ci.org/fengmk2/formstream.png)

![logo](https://raw.github.com/fengmk2/formstream/master/logo.png)

A multipart/form-data encoded stream, helper for file upload.

jscoverage: [-%](http://fengmk2.github.com/coverage/formstream.html)
A [multipart/form-data](http://tools.ietf.org/html/rfc2388) encoded stream, helper for file upload.

jscoverage: [100%](http://fengmk2.github.com/coverage/formstream.html)

## Install

Expand All @@ -20,10 +19,19 @@ $ npm install formstream
var formstream = require('formstream');
var http = require('http');

var req = http.get('http://nodejs.org', function (res) {
res.on('data', function (chunk) {
console.log(charset(res.headers, chunk));
res.destroy();
var form = formstream();
form.file('file', './logo.png');

var options = {
method: 'POST',
host: 'upload.cnodejs.net',
path: '/store',
headers: form.headers()
};
var req = http.request(options, function (res) {
console.log('Status: %s', res.statusCode);
res.on('data', function (data) {
console.log(data.toString());
});
});

Expand Down
10 changes: 6 additions & 4 deletions example/upload.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,18 @@ var path = require('path');
var filepath = __filename;
var imagepath = path.join(path.dirname(__dirname), 'logo.png');

var form = formstream.create();
form.file('file', filepath);
form.stream('image', fs.createReadStream(imagepath));
var form = formstream();
// form.file('file', filepath);
form.stream('file', fs.createReadStream(imagepath), 'logo.png');
form.field('foo', 'hello world');

var req = http.request({
method: 'POST',
host: 'upload.cnodejs.net',
// host: '127.0.0.1',
// port: 8081,
path: '/store',
headers: form.getHeaders()
headers: form.headers()
});
req.on('response', function (res) {
console.log(res.statusCode, res.headers);
Expand Down
97 changes: 74 additions & 23 deletions lib/formstream.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,52 +47,90 @@ function FormStream() {
}

FormStream.super_.call(this);
this._boundary = 'FormStreamBoundary' + Date.now();
this._boundary = this._generateBoundary();
this._streams = [];
this._fields = [];

}
util.inherits(FormStream, Stream);
module.exports = FormStream;

FormStream.create = function () {
return new FormStream();
FormStream.prototype._generateBoundary = function() {
// https://github.com/felixge/node-form-data/blob/master/lib/form_data.js#L162
// This generates a 50 character boundary similar to those used by Firefox.
// They are optimized for boyer-moore parsing.
var boundary = '--------------------------';
for (var i = 0; i < 24; i++) {
boundary += Math.floor(Math.random() * 10).toString(16);
}

return boundary;
};

FormStream.prototype.headers = function (options) {
var headers = {
'Content-Type': 'multipart/form-data; boundary=' + this._boundary
};
if (options) {
for (var k in options) {
headers[k] = options[k];
}
}
return headers;
};

FormStream.prototype.file = function (name, filepath, filename) {
var mimeType = mime.lookup(filepath);
if (!filename) {
filename = path.basename(filepath);
}
this.stream(name, filename, mimeType, fs.createReadStream(filepath));
this.stream(name, fs.createReadStream(filepath), filename, mimeType);
};

FormStream.prototype.field = function (name, value) {
this._fields.push([name, value]);
process.nextTick(this.resume.bind(this));
};

FormStream.prototype.stream = function (name, filename, mimeType, stream) {
FormStream.prototype.stream = function (name, stream, filename, mimeType) {
if (!mimeType) {
// guesss from filename
mimeType = mime.lookup(filename);
}
var ps = parseStream().pause();
stream.pipe(ps);
this._streams.push([ name, filename, mimeType, ps ]);
process.nextTick(this.resume.bind(this));
};

FormStream.prototype.drain = function () {
if (this._fields.length > 0) {
var lines = '';
for (var i = 0; i < this._fields.length; i++) {
var field = this._fields[i];
lines += PADDING + this._boundary + NEW_LINE;
lines += 'Content-Disposition: form-data; name="' + field[0] + '"' + NEW_LINE;
lines += NEW_LINE;
lines += field[1] + NEW_LINE;
}
this._fields = [];
this.emit('data', lines);
FormStream.prototype._emitEnd = function () {
// ending format:
//
// --{boundary}--\r\n
var endData = PADDING + this._boundary + PADDING + NEW_LINE;
this.emit('data', endData);
this.emit('end');
};

FormStream.prototype._emitFields = function () {
if (this._fields.length === 0) {
return;
}
var lines = '';
for (var i = 0; i < this._fields.length; i++) {
var field = this._fields[i];
lines += PADDING + this._boundary + NEW_LINE;
lines += 'Content-Disposition: form-data; name="' + field[0] + '"' + NEW_LINE;
lines += NEW_LINE;
lines += field[1];
lines += NEW_LINE;
}
this._fields = [];
this.emit('data', lines);
};

FormStream.prototype._emitStream = function (item) {
var self = this;
var item = this._streams[0];
var data = PADDING + this._boundary + NEW_LINE;
data += 'Content-Disposition: form-data; name="' + item[0] +'"; filename="' + item[1] + '"' + NEW_LINE;
data += 'Content-Type: ' + item[2] + NEW_LINE;
Expand All @@ -104,16 +142,29 @@ FormStream.prototype.drain = function () {
self.emit('data', data);
});
stream.on('end', function () {
var endData = NEW_LINE;
endData += PADDING + self._boundary + PADDING + NEW_LINE;
self.emit('data', endData);
self.emit('end');
self.emit('data', NEW_LINE);
return process.nextTick(self.drain.bind(self));
});
stream.resume();
};

FormStream.prototype.drain = function () {
this._emitFields();
var item = this._streams.shift();
if (item) {
this._emitStream(item);
} else {
// end
this._emitEnd();
}
return this;
};

FormStream.prototype.resume = function () {
this.paused = false;
this.drain();
if (!this._draining) {
this._draining = true;
this.drain();
}
return this;
};
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,13 @@
"request"
],
"devDependencies": {
"connect": "2.6.0",
"should": "*",
"mocha": "*"
},
"dependencies": {
"mime": "1.2.7",
"pause-stream": "0.0.5"
"pause-stream": "0.0.6"
},
"author": "fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)",
"license": "MIT"
Expand Down
39 changes: 39 additions & 0 deletions test/fixtures/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*!
* formstream - test/fixtures/server.js
* Copyright(c) 2012 fengmk2 <fengmk2@gmail.com>
* MIT Licensed
*/

"use strict";

/**
* Module dependencies.
*/

var connect = require('connect');

var app = connect(
connect.bodyParser(),
function (req, res, next) {
var files = {};
for (var k in req.files) {
var f = req.files[k];
files[k] = {
size: f.length,
mime: f.mime,
filename: f.filename,
path: f.path
};
}
// console.log(files)
res.end(JSON.stringify({
url: req.url,
method: req.method,
headers: req.headers,
body: req.body,
files: files
}));
}
);

module.exports = app;
Loading

0 comments on commit 5a430fb

Please sign in to comment.