Permalink
Browse files

support multi streams.

  • Loading branch information...
1 parent fc8fd67 commit 5a430fb8d0393aec231e439b4ff1ed3e27364aae @fengmk2 fengmk2 committed Oct 11, 2012
Showing with 298 additions and 59 deletions.
  1. +2 −1 .gitignore
  2. +0 −1 .npmignore
  3. +15 −7 README.md
  4. +6 −4 example/upload.js
  5. +74 −23 lib/formstream.js
  6. +2 −1 package.json
  7. +39 −0 test/fixtures/server.js
  8. +160 −22 test/formstream.test.js
View
@@ -1,4 +1,5 @@
lib-cov
+coverage.html
*.seed
*.log
*.csv
@@ -12,4 +13,4 @@ logs
results
node_modules
-npm-debug.log
+npm-debug.log
View
@@ -4,4 +4,3 @@ lib-cov/
Makefile
.travis.yml
logo.png
-example/
View
@@ -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
@@ -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());
});
});
View
@@ -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);
View
@@ -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;
@@ -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;
};
View
@@ -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"
View
@@ -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;
Oops, something went wrong.

0 comments on commit 5a430fb

Please sign in to comment.