From 9e7b1b59933cb5777152252b80b9fd5c0403d178 Mon Sep 17 00:00:00 2001 From: Nick Uraltsev Date: Tue, 12 Apr 2016 19:49:12 -0700 Subject: [PATCH 1/3] Add isFunction and isStream helpers --- lib/utils.js | 22 ++++++++++++++++++++++ test/specs/utils/isX.spec.js | 12 +++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/lib/utils.js b/lib/utils.js index 0d418ae..33f2d4d 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -122,6 +122,26 @@ function isBlob(val) { return toString.call(val) === '[object Blob]'; } +/** + * Determine if a value is a Function + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a Function, otherwise false + */ +function isFunction(val) { + return toString.call(val) === '[object Function]'; +} + +/** + * Determine if a value is a Stream + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a Stream, otherwise false + */ +function isStream(val) { + return isObject(val) && isFunction(val.pipe); +} + /** * Trim excess whitespace off the beginning and end of a string * @@ -237,6 +257,8 @@ module.exports = { isDate: isDate, isFile: isFile, isBlob: isBlob, + isFunction: isFunction, + isStream: isStream, isStandardBrowserEnv: isStandardBrowserEnv, forEach: forEach, merge: merge, diff --git a/test/specs/utils/isX.spec.js b/test/specs/utils/isX.spec.js index 88a0f2b..d58d113 100644 --- a/test/specs/utils/isX.spec.js +++ b/test/specs/utils/isX.spec.js @@ -1,4 +1,5 @@ var utils = require('../../../lib/utils'); +var Stream = require('stream'); describe('utils::isX', function () { it('should validate Array', function () { @@ -67,5 +68,14 @@ describe('utils::isX', function () { expect(utils.isDate(new Date())).toEqual(true); expect(utils.isDate(Date.now())).toEqual(false); }); -}); + it('should validate Function', function () { + expect(utils.isFunction(function () {})).toEqual(true); + expect(utils.isFunction('function')).toEqual(false); + }); + + it('should validate Stream', function () { + expect(utils.isStream(new Stream.Readable())).toEqual(true); + expect(utils.isStream({ foo: 'bar' })).toEqual(false); + }); +}); From d23f9d5d4782e5849362895f8b648ed587999706 Mon Sep 17 00:00:00 2001 From: Nick Uraltsev Date: Thu, 14 Apr 2016 17:38:35 -0700 Subject: [PATCH 2/3] Add support for Stream --- lib/adapters/http.js | 75 +++++++++++++++++++++----------------- lib/defaults.js | 9 ++--- test/unit/adapters/http.js | 26 ++++++++++++- 3 files changed, 69 insertions(+), 41 deletions(-) diff --git a/lib/adapters/http.js b/lib/adapters/http.js index 3c3970a..84be87b 100644 --- a/lib/adapters/http.js +++ b/lib/adapters/http.js @@ -10,6 +10,13 @@ var zlib = require('zlib'); var pkg = require('./../../package.json'); var Buffer = require('buffer').Buffer; +// Resolve or reject the Promise based on the status +function settle(resolve, reject, response) { + (response.status >= 200 && response.status < 300 ? + resolve : + reject)(response); +} + /*eslint consistent-return:0*/ module.exports = function httpAdapter(resolve, reject, config) { var data = config.data; @@ -24,13 +31,13 @@ module.exports = function httpAdapter(resolve, reject, config) { headers['User-Agent'] = 'axios/' + pkg.version; } - if (data) { + if (data && !utils.isStream(data)) { if (utils.isArrayBuffer(data)) { data = new Buffer(new Uint8Array(data)); } else if (utils.isString(data)) { data = new Buffer(data, 'utf-8'); } else { - return reject(new Error('Data after transformation must be a string or an ArrayBuffer')); + return reject(new Error('Data after transformation must be a string, an ArrayBuffer, or a Stream')); } // Add Content-Length header if data exists @@ -93,39 +100,37 @@ module.exports = function httpAdapter(resolve, reject, config) { break; } - var responseBuffer = []; - stream.on('data', function handleStreamData(chunk) { - responseBuffer.push(chunk); + var response = { + status: res.statusCode, + statusText: res.statusMessage, + headers: res.headers, + config: config, + request: req + }; - // make sure the content length is not over the maxContentLength if specified - if (config.maxContentLength > -1 && Buffer.concat(responseBuffer).length > config.maxContentLength) { - reject(new Error('maxContentLength size of ' + config.maxContentLength + ' exceeded')); - } - }); + if (config.responseType === 'stream') { + response.data = stream; + settle(resolve, reject, response); + } else { + var responseBuffer = []; + stream.on('data', function handleStreamData(chunk) { + responseBuffer.push(chunk); - stream.on('end', function handleStreamEnd() { - var d = Buffer.concat(responseBuffer); - if (config.responseType !== 'arraybuffer') { - d = d.toString('utf8'); - } - var response = { - data: transformData( - d, - res.headers, - config.transformResponse - ), - status: res.statusCode, - statusText: res.statusMessage, - headers: res.headers, - config: config, - request: req - }; + // make sure the content length is not over the maxContentLength if specified + if (config.maxContentLength > -1 && Buffer.concat(responseBuffer).length > config.maxContentLength) { + reject(new Error('maxContentLength size of ' + config.maxContentLength + ' exceeded')); + } + }); - // Resolve or reject the Promise based on the status - (res.statusCode >= 200 && res.statusCode < 300 ? - resolve : - reject)(response); - }); + stream.on('end', function handleStreamEnd() { + var responseData = Buffer.concat(responseBuffer); + if (config.responseType !== 'arraybuffer') { + responseData = responseData.toString('utf8'); + } + response.data = transformData(responseData, res.headers, config.transformResponse); + settle(resolve, reject, response); + }); + } }); // Handle errors @@ -147,5 +152,9 @@ module.exports = function httpAdapter(resolve, reject, config) { } // Send the request - req.end(data); + if (utils.isStream(data)) { + data.pipe(req); + } else { + req.end(data); + } }; diff --git a/lib/defaults.js b/lib/defaults.js index 45910c8..6bbfcbf 100644 --- a/lib/defaults.js +++ b/lib/defaults.js @@ -8,11 +8,8 @@ var DEFAULT_CONTENT_TYPE = { }; module.exports = { - transformRequest: [function transformRequestJSON(data, headers) { - if (utils.isFormData(data)) { - return data; - } - if (utils.isArrayBuffer(data)) { + transformRequest: [function transformRequest(data, headers) { + if (utils.isFormData(data) || utils.isArrayBuffer(data) || utils.isStream(data)) { return data; } if (utils.isArrayBufferView(data)) { @@ -36,7 +33,7 @@ module.exports = { return data; }], - transformResponse: [function transformResponseJSON(data) { + transformResponse: [function transformResponse(data) { /*eslint no-param-reassign:0*/ if (typeof data === 'string') { data = data.replace(PROTECTION_PREFIX, ''); diff --git a/test/unit/adapters/http.js b/test/unit/adapters/http.js index b7f2086..acd0b30 100644 --- a/test/unit/adapters/http.js +++ b/test/unit/adapters/http.js @@ -2,6 +2,7 @@ var axios = require('../../../index'); var http = require('http'); var url = require('url'); var zlib = require('zlib'); +var fs = require('fs'); var server; module.exports = { @@ -127,7 +128,7 @@ module.exports = { }); }); }, - + testMaxContentLength: function(test) { var str = Array(100000).join('ж'); @@ -145,7 +146,7 @@ module.exports = { error = res; failure = true; }); - + setTimeout(function () { test.equal(success, false, 'request should not succeed'); test.equal(failure, true, 'request should fail'); @@ -153,5 +154,26 @@ module.exports = { test.done(); }, 100); }); + }, + + testStream: function(test) { + server = http.createServer(function (req, res) { + req.pipe(res); + }).listen(4444, function () { + axios.post('http://localhost:4444/', + fs.createReadStream(__filename), { + responseType: 'stream' + }).then(function (res) { + var stream = res.data; + var string = ''; + stream.on('data', function (chunk) { + string += chunk.toString('utf8'); + }); + stream.on('end', function () { + test.equal(string, fs.readFileSync(__filename, 'utf8')); + test.done(); + }); + }); + }); } }; From d88bc12e59ecfb8c2e1e77f7ecb545154575e389 Mon Sep 17 00:00:00 2001 From: Nick Uraltsev Date: Thu, 14 Apr 2016 21:04:22 -0700 Subject: [PATCH 3/3] Update README.md --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index be573d6..8368f89 100644 --- a/README.md +++ b/README.md @@ -180,18 +180,18 @@ These are the available config options for making requests. Only the `url` is re { // `url` is the server URL that will be used for the request url: '/user', - + // `method` is the request method to be used when making the request method: 'get', // default - // `baseURL` will be prepended to `url` unless `url` is absolute. - // It can be convenient to set `baseURL` for an instance of axios to pass relative URLs + // `baseURL` will be prepended to `url` unless `url` is absolute. + // It can be convenient to set `baseURL` for an instance of axios to pass relative URLs // to methods of that instance. baseURL: 'https://some-domain.com/api/', // `transformRequest` allows changes to the request data before it is sent to the server // This is only applicable for request methods 'PUT', 'POST', and 'PATCH' - // The last function in the array must return a string or an ArrayBuffer + // The last function in the array must return a string, an ArrayBuffer, or a Stream transformRequest: [function (data) { // Do whatever you want to transform the data @@ -222,7 +222,7 @@ These are the available config options for making requests. Only the `url` is re // `data` is the data to be sent as the request body // Only applicable for request methods 'PUT', 'POST', and 'PATCH' - // When no `transformRequest` is set, must be a string, an ArrayBuffer or a hash + // When no `transformRequest` is set, must be a string, an ArrayBuffer, a hash, or a Stream data: { firstName: 'Fred' }, @@ -250,7 +250,7 @@ These are the available config options for making requests. Only the `url` is re } // `responseType` indicates the type of data that the server will respond with - // options are 'arraybuffer', 'blob', 'document', 'json', 'text' + // options are 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream' responseType: 'json', // default // `xsrfCookieName` is the name of the cookie to use as a value for xsrf token @@ -264,7 +264,7 @@ These are the available config options for making requests. Only the `url` is re progress: function(progressEvent) { // Do whatever you want with the native progress event }, - + // `maxContentLength` defines the max size of the http response content allowed maxContentLength: 2000 } @@ -346,7 +346,7 @@ instance.defaults.timeout = 2500; // Override timeout for this request as it's known to take a long time instance.get('/longRequest', { timeout: 5000 -}); +}); ``` ## Interceptors