Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Feature: incomingForm 'fileBegin' event

This event is useful if you need to buffer the uploaded file to disk, but
you are interested in streaming it to somewhere else while it is still
being uploaded.

You may need this if you are streaming the file to a destination that is
potentially slower at receiving it than the client that is uploading. By
buffering to disk you can ensure maximum upload speed for the client, while
having the flexibility to streaming the file on disk with a different speed.
  • Loading branch information...
commit cf45cdcd32e3a564c251252a6e8bf90f319da367 1 parent 77b9465
@felixge authored
View
32 Readme.md
@@ -136,14 +136,15 @@ Emitted after each incoming chunk of data that has been parsed. Can be used to r
Emitted whenever a field / value pair has been received.
-#### Event: 'file' (name, file)
+#### Event: 'fileBegin' (name, file)
-Emitted whenever a field / file pair has been received. `file` is a JS object with the following properties:
+Emitted whenever a new file is detected in the upload stream. Use this even if
+you want to stream the file to somewhere else while buffering the upload on
+the file system.
- { path: 'the path in the uploadDir this file was written to'
- , filename: 'the name this file had on the computer of the uploader'
- , mime: 'the mime type specified by the user agent of the uploader'
- }
+#### Event: 'file' (name, file)
+
+Emitted whenever a field / file pair has been received. `file` is an instance of `File`.
#### Event: 'error' (err)
@@ -153,6 +154,25 @@ Emitted when there is an error processing the incoming form. A request that expe
Emitted when the entire request has been received, and all contained files have finished flushing to disk. This is a great place for you to send your response.
+### formdiable.File
+
+#### file.length = 0
+
+The size of the uploade file in bytes. If the file is still being uploaded (see `'fileBegin'` event), this property says how many bytes of the file have been written to disk yet.
+
+#### file.path = null
+
+The path this file is being written to. You can modify this in the `'fileBegin'` event in
+case you are unhappy with the way formidable generates a temporary path for your files.
+
+#### file.filename = null
+
+The name this file had according to the uploading client.
+
+#### file.mime = null
+
+The mime type of this file, according to the uploading client.
+
## License
Formidable is licensed under the MIT license.
View
43 lib/formidable/file.js
@@ -0,0 +1,43 @@
+if (global.GENTLY) require = GENTLY.hijack(require);
+
+var util = require('./util'),
+ WriteStream = require('fs').WriteStream,
+ EventEmitter = require('events').EventEmitter;
+
+function File(properties) {
+ EventEmitter.call(this);
+
+ this.length = 0;
+ this.path = null;
+ this.filename = null;
+ this.mime = null;
+
+ this._writeStream = null;
+
+ for (var key in properties) {
+ this[key] = properties[key];
+ }
+}
+module.exports = File;
+util.inherits(File, EventEmitter);
+
+File.prototype.open = function() {
+ this._writeStream = new WriteStream(this.path);
+};
+
+File.prototype.write = function(buffer, cb) {
+ var self = this;
+ this._writeStream.write(buffer, function() {
+ self.length += buffer.length;
+ self.emit('progress', self.length);
+ cb();
+ });
+};
+
+File.prototype.end = function(cb) {
+ var self = this;
+ this._writeStream.end(function() {
+ self.emit('end');
+ cb();
+ });
+};
View
18 lib/formidable/incoming_form.js
@@ -2,7 +2,7 @@ if (global.GENTLY) require = GENTLY.hijack(require);
var util = require('./util')
, path = require('path')
- , WriteStream = require('fs').WriteStream
+ , File = require('./file')
, MultipartParser = require('./multipart_parser').MultipartParser
, QuerystringParser = require('./querystring_parser').QuerystringParser
, StringDecoder = require('string_decoder').StringDecoder
@@ -165,7 +165,16 @@ IncomingForm.prototype.handlePart = function(part) {
this._flushing++;
- var file = new WriteStream(this._uploadPath(part.filename));
+ var file = new File({
+ path: this._uploadPath(part.filename),
+ filename: part.filename,
+ mime: part.mime,
+ });
+
+ this.emit('fileBegin', part.name, file);
+
+ file.open();
+
part.addListener('data', function(buffer) {
self.pause();
file.write(buffer, function() {
@@ -179,10 +188,7 @@ IncomingForm.prototype.handlePart = function(part) {
self.emit
( 'file'
, part.name
- , { path: file.path
- , filename: part.filename
- , mime: part.mime
- }
+ , file
);
self._maybeEnd();
});
View
102 test/simple/test-file.js
@@ -0,0 +1,102 @@
+require('../common');
+var WriteStreamStub = GENTLY.stub('fs', 'WriteStream');
+
+var File = require('formidable/file'),
+ EventEmitter = require('events').EventEmitter,
+ file,
+ gently;
+
+function test(test) {
+ gently = new Gently();
+ file = new File();
+ test();
+ gently.verify(test.name);
+}
+
+test(function constructor() {
+ assert.ok(file instanceof EventEmitter);
+ assert.strictEqual(file.length, 0);
+ assert.strictEqual(file.path, null);
+ assert.strictEqual(file.filename, null);
+ assert.strictEqual(file.mime, null);
+
+ assert.strictEqual(file._writeStream, null);
+
+ (function testSetProperties() {
+ var file2 = new File({foo: 'bar'});
+ assert.equal(file2.foo, 'bar');
+ })();
+});
+
+test(function open() {
+ var WRITE_STREAM;
+ file.path = '/foo';
+
+ gently.expect(WriteStreamStub, 'new', function (path) {
+ WRITE_STREAM = this;
+ assert.strictEqual(path, file.path);
+ });
+
+ file.open();
+ assert.strictEqual(file._writeStream, WRITE_STREAM);
+});
+
+test(function write() {
+ var BUFFER = {length: 10},
+ CB_STUB,
+ CB = function() {
+ CB_STUB.apply(this, arguments);
+ };
+
+ file._writeStream = {};
+
+ gently.expect(file._writeStream, 'write', function (buffer, cb) {
+ assert.strictEqual(buffer, BUFFER);
+
+ gently.expect(file, 'emit', function (event, bytesWritten) {
+ assert.equal(event, 'progress');
+ assert.equal(bytesWritten, file.length);
+ });
+
+ CB_STUB = gently.expect(function writeCb() {
+ assert.equal(file.length, 10);
+ });
+
+ cb();
+
+ gently.expect(file, 'emit', function (event, bytesWritten) {
+ assert.equal(event, 'progress');
+ assert.equal(bytesWritten, file.length);
+ });
+
+ CB_STUB = gently.expect(function writeCb() {
+ assert.equal(file.length, 20);
+ });
+
+ cb();
+ });
+
+ file.write(BUFFER, CB);
+});
+
+test(function end() {
+ var CB_STUB,
+ CB = function() {
+ CB_STUB.apply(this, arguments);
+ };
+
+ file._writeStream = {};
+
+ gently.expect(file._writeStream, 'end', function (cb) {
+ gently.expect(file, 'emit', function (event) {
+ assert.equal(event, 'end');
+ });
+
+ CB_STUB = gently.expect(function endCb() {
+ });
+
+ cb();
+ });
+
+ file.end(CB);
+});
View
24 test/simple/test-incoming-form.js
@@ -2,7 +2,7 @@ require('../common');
var MultipartParserStub = GENTLY.stub('./multipart_parser', 'MultipartParser')
, QuerystringParserStub = GENTLY.stub('./querystring_parser', 'QuerystringParser')
, EventEmitterStub = GENTLY.stub('events', 'EventEmitter')
- , WriteStreamStub = GENTLY.stub('fs', 'WriteStream');
+ , FileStub = GENTLY.stub('./file');
var formidable = require('formidable')
, IncomingForm = formidable.IncomingForm
@@ -594,9 +594,19 @@ test(function handlePart() {
return PATH;
});
- gently.expect(WriteStreamStub, 'new', function(path) {
- assert.equal(path, PATH);
+ gently.expect(FileStub, 'new', function(properties) {
+ assert.equal(properties.path, PATH);
+ assert.equal(properties.filename, PART.filename);
+ assert.equal(properties.mime, PART.mime);
FILE = this;
+
+ gently.expect(form, 'emit', function (event, field, file) {
+ assert.equal(event, 'fileBegin');
+ assert.strictEqual(field, PART.name);
+ assert.strictEqual(file, FILE);
+ });
+
+ gently.expect(FILE, 'open');
});
form.handlePart(PART);
@@ -616,13 +626,7 @@ test(function handlePart() {
gently.expect(FILE, 'end', function(cb) {
gently.expect(form, 'emit', function(event, field, file) {
assert.equal(event, 'file');
- assert.deepEqual
- ( file
- , { path: FILE.path
- , filename: PART.filename
- , mime: PART.mime
- }
- );
+ assert.strictEqual(file, FILE);
});
gently.expect(form, '_maybeEnd');
View
32 test/system/test-multi-video-upload.js
@@ -9,27 +9,43 @@ var BOUNDARY = '---------------------------10102754414578508781458777923'
server.addListener('request', function(req, res) {
var form = new formidable.IncomingForm()
- , files = {};
+ , uploads = {};
form.uploadDir = TEST_TMP;
form.parse(req);
form
+ .addListener('fileBegin', function(field, file) {
+ assert.equal(field, 'upload');
+
+ var tracker = {file: file, progress: [], ended: false};
+ uploads[file.filename] = tracker;
+ file
+ .on('progress', function(bytesReceived) {
+ tracker.progress.push(bytesReceived);
+ assert.equal(bytesReceived, file.length);
+ })
+ .on('end', function() {
+ tracker.ended = true;
+ });
+ })
.addListener('field', function(field, value) {
assert.equal(field, 'title');
assert.equal(value, '');
})
.addListener('file', function(field, file) {
assert.equal(field, 'upload');
- files[file.filename] = true;
+ assert.strictEqual(uploads[file.filename].file, file);
})
.addListener('end', function() {
- assert.deepEqual
- ( files
- , { 'shortest_video.flv': true
- , 'shortest_video.mp4' :true
- }
- );
+ assert.ok(uploads['shortest_video.flv']);
+ assert.ok(uploads['shortest_video.flv'].ended);
+ assert.ok(uploads['shortest_video.flv'].progress.length > 3);
+ assert.equal(uploads['shortest_video.flv'].progress.slice(-1), uploads['shortest_video.flv'].file.length);
+ assert.ok(uploads['shortest_video.mp4']);
+ assert.ok(uploads['shortest_video.mp4'].ended);
+ assert.ok(uploads['shortest_video.mp4'].progress.length > 3);
+
server.close();
res.writeHead(200);
res.end('good');
Please sign in to comment.
Something went wrong with that request. Please try again.