diff --git a/Readme.md b/Readme.md index 7bfea3e..b09df6a 100644 --- a/Readme.md +++ b/Readme.md @@ -218,7 +218,7 @@ form.append( 'my_file', fs.createReadStream('/foo/bar.jpg'), {filename: 'bar.jpg ``` #### _Headers_ getHeaders( [**Headers** _userHeaders_] ) -This method adds the correct `content-type` header to the provided array of `userHeaders`. +This method adds the correct `content-type` header to the provided array of `userHeaders`. #### _String_ getBoundary() Return the boundary of the formData. By default, the boundary consists of 26 `-` followed by 24 numbers @@ -348,6 +348,8 @@ axios.post('http://example.com', form, { ## Notes - ```getLengthSync()``` method DOESN'T calculate length for streams, use ```knownLength``` options as workaround. +- ```getLength(cb)``` will send an error as first parameter of callback if stream length cannot be calculated (e.g. send in custom streams w/o using ```knownLength```). +- ```submit``` will not add `content-length` if form length is unknown or not calculable. - Starting version `2.x` FormData has dropped support for `node@0.10.x`. - Starting version `3.x` FormData has dropped support for `node@4.x`. diff --git a/lib/form_data.js b/lib/form_data.js index cf836b0..18dc819 100644 --- a/lib/form_data.js +++ b/lib/form_data.js @@ -5,6 +5,7 @@ var http = require('http'); var https = require('https'); var parseUrl = require('url').parse; var fs = require('fs'); +var Stream = require('stream').Stream; var mime = require('mime-types'); var asynckit = require('asynckit'); var populate = require('./populate.js'); @@ -100,8 +101,8 @@ FormData.prototype._trackLength = function(header, value, options) { Buffer.byteLength(header) + FormData.LINE_BREAK.length; - // empty or either doesn't have path or not an http response - if (!value || ( !value.path && !(value.readable && value.hasOwnProperty('httpVersion')) )) { + // empty or either doesn't have path or not an http response or not a stream + if (!value || ( !value.path && !(value.readable && value.hasOwnProperty('httpVersion')) && !(value instanceof Stream))) { return; } @@ -456,13 +457,15 @@ FormData.prototype.submit = function(params, cb) { // get content length and fire away this.getLength(function(err, length) { - if (err) { + if (err && err !== 'Unknown stream') { this._error(err); return; } // add content length - request.setHeader('Content-Length', length); + if (length) { + request.setHeader('Content-Length', length); + } this.pipe(request); if (cb) { diff --git a/test/integration/test-form-get-length-sync.js b/test/integration/test-form-get-length-sync.js index e212a76..9901c7e 100644 --- a/test/integration/test-form-get-length-sync.js +++ b/test/integration/test-form-get-length-sync.js @@ -2,6 +2,7 @@ var common = require('../common'); var assert = common.assert; var FormData = require(common.dir.lib + '/form_data'); var fs = require('fs'); +var Readable = require('stream').Readable; (function testGetLengthSync() { var fields = [ @@ -73,3 +74,34 @@ var fs = require('fs'); assert.equal(expectedLength, calculatedLength); })(); + +(function testReadableStreamData() { + var form = new FormData(); + + var util = require('util'); + util.inherits(CustomReadable, Readable); + + /** + * Custion readable constructor + * @param {Object} opt options + * @constructor + */ + function CustomReadable(opt) { + Readable.call(this, opt); + this._max = 2; + this._index = 1; + } + + CustomReadable.prototype._read = function() { + var i = this._index++; + if (i > this._max) { + this.push(null); + } else { + this.push('' + i); + } + }; + form.append('my_txt', new CustomReadable()); + + assert.throws(function() { form.getLengthSync(); }, /Cannot calculate proper length in synchronous way/); + +})(); diff --git a/test/integration/test-form-get-length.js b/test/integration/test-form-get-length.js index 6b03069..6dabca7 100644 --- a/test/integration/test-form-get-length.js +++ b/test/integration/test-form-get-length.js @@ -3,6 +3,7 @@ var assert = common.assert; var FormData = require(common.dir.lib + '/form_data'); var fake = require('fake').create(); var fs = require('fs'); +var Readable = require('stream').Readable; (function testEmptyForm() { var form = new FormData(); @@ -89,3 +90,40 @@ var fs = require('fs'); fake.expectAnytime(callback, [null, expectedLength]); form.getLength(callback); })(); + +(function testReadableStreamData() { + var form = new FormData(); + // var expectedLength = 0; + + var util = require('util'); + util.inherits(CustomReadable, Readable); + + /** + * Custion readable constructor + * @param {Object} opt options + * @constructor + */ + function CustomReadable(opt) { + Readable.call(this, opt); + this._max = 2; + this._index = 1; + } + + CustomReadable.prototype._read = function() { + var i = this._index++; + if (i > this._max) { + this.push(null); + } else { + this.push('' + i); + } + }; + form.append('my_txt', new CustomReadable()); + + // expectedLength += form._overheadLength + form._lastBoundary().length; + + // there is no way to determine the length of this readable stream. + var callback = fake.callback(arguments.callee.name + '-getLength'); + fake.expectAnytime(callback, ['Unknown stream', undefined]); + form.getLength(function(err, len) { callback(err,len); }); + +})(); diff --git a/test/integration/test-submit-readable-stream.js b/test/integration/test-submit-readable-stream.js new file mode 100644 index 0000000..41896a4 --- /dev/null +++ b/test/integration/test-submit-readable-stream.js @@ -0,0 +1,54 @@ +var common = require('../common'); +var assert = common.assert; +var http = require('http'); +var FormData = require(common.dir.lib + '/form_data'); +var Readable = require('stream').Readable; + +var server = http.createServer(function(req, res) { + assert.strictEqual(req.headers['Content-Length'], undefined); + res.writeHead(200); + res.end('done'); +}); + +server.listen(common.port, function() { + var form = new FormData(); + + var util = require('util'); + util.inherits(CustomReadable, Readable); + + /** + * Custion readable constructor + * @param {Object} opt options + * @constructor + */ + function CustomReadable(opt) { + Readable.call(this, opt); + this._max = 2; + this._index = 1; + } + + CustomReadable.prototype._read = function() { + var i = this._index++; + // console.error('send back read data'); + if (i > this._max) { + this.push(null); + } else { + this.push('' + i); + } + }; + form.append('readable', new CustomReadable()); + + form.submit('http://localhost:' + common.port + '/', function(err, res) { + if (err) { + throw err; + } + + assert.strictEqual(res.statusCode, 200); + + // unstuck new streams + res.resume(); + + server.close(); + }); + +});