From e658483ebefef4fff724746a0869a94c5465abb0 Mon Sep 17 00:00:00 2001 From: Kirill Konshin Date: Mon, 10 Aug 2015 12:35:01 -0700 Subject: [PATCH] Fix for #38 and #36 Support string/buffer bodies in Response constructor Request should have Body methods like text(), json() --- index.js | 3 +- lib/body.js | 192 ++++++++++++++++++++++++++++++++++++++++++++++++ lib/headers.js | 3 +- lib/request.js | 12 ++- lib/response.js | 156 +-------------------------------------- test/test.js | 32 ++++++++ 6 files changed, 239 insertions(+), 159 deletions(-) create mode 100644 lib/body.js diff --git a/index.js b/index.js index ca2b452..5f9be58 100644 --- a/index.js +++ b/index.js @@ -12,6 +12,7 @@ var https = require('https'); var zlib = require('zlib'); var stream = require('stream'); +var Body = require('./lib/body'); var Response = require('./lib/response'); var Headers = require('./lib/headers'); var Request = require('./lib/request'); @@ -36,7 +37,7 @@ function Fetch(url, opts) { throw new Error('native promise missing, set Fetch.Promise to your favorite alternative'); } - Response.Promise = Fetch.Promise; + Body.Promise = Fetch.Promise; var self = this; diff --git a/lib/body.js b/lib/body.js new file mode 100644 index 0000000..7c15f91 --- /dev/null +++ b/lib/body.js @@ -0,0 +1,192 @@ +/** + * response.js + * + * Response class provides content decoding + */ + +var convert = require('encoding').convert; + +module.exports = Body; + +/** + * Response class + * + * @param Stream body Readable stream + * @param Object opts Response options + * @return Void + */ +function Body(body, opts) { + + opts = opts || {}; + + this.body = body; + this.bodyUsed = false; + this.size = opts.size || 0; + this.timeout = opts.timeout || 0; + this._raw = []; + this._abort = false; + +} + +/** + * Decode response as json + * + * @return Promise + */ +Body.prototype.json = function() { + + return this._decode().then(function(text) { + return JSON.parse(text); + }); + +}; + +/** + * Decode response as text + * + * @return Promise + */ +Body.prototype.text = function() { + + return this._decode(); + +}; + +/** + * Decode buffers into utf-8 string + * + * @return Promise + */ +Body.prototype._decode = function() { + + var self = this; + + if (this.bodyUsed) { + return Body.Promise.reject(new Error('body used already for: ' + this.url)); + } + + this.bodyUsed = true; + this._bytes = 0; + this._abort = false; + this._raw = []; + + return new Body.Promise(function(resolve, reject) { + var resTimeout; + + if (typeof self.body === 'string') { + self._bytes = self.body.length; + self._raw = [new Buffer(self.body)]; + return resolve(self._convert()); + } + + if (self.body instanceof Buffer) { + self._bytes = self.body.length; + self._raw = [self.body]; + return resolve(self._convert()); + } + + // allow timeout on slow response body + if (self.timeout) { + resTimeout = setTimeout(function() { + self._abort = true; + reject(new Error('response timeout at ' + self.url + ' over limit: ' + self.timeout)); + }, self.timeout); + } + + // handle stream error, such as incorrect content-encoding + self.body.on('error', function(err) { + reject(new Error('invalid response body at: ' + self.url + ' reason: ' + err.message)); + }); + + self.body.on('data', function(chunk) { + if (self._abort || chunk === null) { + return; + } + + if (self.size && self._bytes + chunk.length > self.size) { + self._abort = true; + reject(new Error('content size at ' + self.url + ' over limit: ' + self.size)); + return; + } + + self._bytes += chunk.length; + self._raw.push(chunk); + }); + + self.body.on('end', function() { + if (self._abort) { + return; + } + + clearTimeout(resTimeout); + resolve(self._convert()); + }); + }); + +}; + +/** + * Detect buffer encoding and convert to target encoding + * ref: http://www.w3.org/TR/2011/WD-html5-20110113/parsing.html#determining-the-character-encoding + * + * @param String encoding Target encoding + * @return String + */ +Body.prototype._convert = function(encoding) { + + encoding = encoding || 'utf-8'; + + var charset = 'utf-8'; + var res, str; + + // header + if (this.headers.has('content-type')) { + res = /charset=([^;]*)/i.exec(this.headers.get('content-type')); + } + + // no charset in content type, peek at response body + if (!res && this._raw.length > 0) { + str = this._raw[0].toString().substr(0, 1024); + } + + // html5 + if (!res && str) { + res = /= 200 && this.status < 300; - this.timeout = opts.timeout; + + Body.call(this, body, opts); } -/** - * Decode response as json - * - * @return Promise - */ -Response.prototype.json = function() { - - return this._decode().then(function(text) { - return JSON.parse(text); - }); - -} - -/** - * Decode response as text - * - * @return Promise - */ -Response.prototype.text = function() { - - return this._decode(); - -} - -/** - * Decode buffers into utf-8 string - * - * @return Promise - */ -Response.prototype._decode = function() { - - var self = this; - - if (this.bodyUsed) { - return Response.Promise.reject(new Error('body used already for: ' + this.url)); - } - - this.bodyUsed = true; - this._bytes = 0; - this._abort = false; - this._raw = []; - - return new Response.Promise(function(resolve, reject) { - var resTimeout; - - // allow timeout on slow response body - if (self.timeout) { - resTimeout = setTimeout(function() { - self._abort = true; - reject(new Error('response timeout at ' + self.url + ' over limit: ' + self.timeout)); - }, self.timeout); - } - - // handle stream error, such as incorrect content-encoding - self.body.on('error', function(err) { - reject(new Error('invalid response body at: ' + self.url + ' reason: ' + err.message)); - }); - - self.body.on('data', function(chunk) { - if (self._abort || chunk === null) { - return; - } - - if (self.size && self._bytes + chunk.length > self.size) { - self._abort = true; - reject(new Error('content size at ' + self.url + ' over limit: ' + self.size)); - return; - } - - self._bytes += chunk.length; - self._raw.push(chunk); - }); - - self.body.on('end', function() { - if (self._abort) { - return; - } - - clearTimeout(resTimeout); - resolve(self._convert()); - }); - }); - -}; - -/** - * Detect buffer encoding and convert to target encoding - * ref: http://www.w3.org/TR/2011/WD-html5-20110113/parsing.html#determining-the-character-encoding - * - * @param String encoding Target encoding - * @return String - */ -Response.prototype._convert = function(encoding) { - - encoding = encoding || 'utf-8'; - - var charset = 'utf-8'; - var res, str; - - // header - if (this.headers.has('content-type')) { - res = /charset=([^;]*)/i.exec(this.headers.get('content-type')); - } - - // no charset in content type, peek at response body - if (!res && this._raw.length > 0) { - str = this._raw[0].toString().substr(0, 1024); - } - - // html5 - if (!res && str) { - res = /