diff --git a/index.js b/index.js index 713b66d..aa490aa 100644 --- a/index.js +++ b/index.js @@ -159,6 +159,7 @@ function Fetch(url, opts) { , status: res.statusCode , headers: headers , size: options.size + , timeout: options.timeout }); resolve(output); diff --git a/lib/response.js b/lib/response.js index 1c109d3..77e226c 100644 --- a/lib/response.js +++ b/lib/response.js @@ -27,6 +27,7 @@ function Response(body, opts) { this.bodyUsed = false; this.size = opts.size; this.ok = this.status >= 200 && this.status < 300; + this.timeout = opts.timeout; } @@ -73,6 +74,16 @@ Response.prototype._decode = function() { 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); + } + self.body.on('data', function(chunk) { if (self._abort || chunk === null) { return; @@ -93,6 +104,7 @@ Response.prototype._decode = function() { return; } + clearTimeout(resTimeout); resolve(self._convert()); }); }); diff --git a/test/server.js b/test/server.js index 56c8dad..1d17f5d 100644 --- a/test/server.js +++ b/test/server.js @@ -87,6 +87,15 @@ TestServer.prototype.router = function(req, res) { }, 1000); } + if (p === '/slow') { + res.statusCode = 200; + res.setHeader('Content-Type', 'text/plain'); + res.write('test'); + setTimeout(function() { + res.end('test'); + }, 1000); + } + if (p === '/cookie') { res.statusCode = 200; res.setHeader('Set-Cookie', ['a=1', 'b=1']); diff --git a/test/test.js b/test/test.js index e113cab..9942875 100644 --- a/test/test.js +++ b/test/test.js @@ -316,6 +316,7 @@ describe('node-fetch', function() { }); it('should allow custom timeout', function() { + this.timeout(500); url = base + '/timeout'; opts = { timeout: 100 @@ -323,6 +324,42 @@ describe('node-fetch', function() { return expect(fetch(url, opts)).to.eventually.be.rejectedWith(Error); }); + it('should allow custom timeout on response body', function() { + this.timeout(500); + url = base + '/slow'; + opts = { + timeout: 100 + }; + return fetch(url, opts).then(function(res) { + expect(res.ok).to.be.true; + return expect(res.text()).to.eventually.be.rejectedWith(Error); + }); + }); + + it('should clear internal timeout on fetch response', function (done) { + this.timeout(1000); + spawn('node', ['-e', 'require("./")("' + base + '/hello", { timeout: 5000 })']) + .on('exit', function () { + done(); + }); + }); + + it('should clear internal timeout on fetch redirect', function (done) { + this.timeout(1000); + spawn('node', ['-e', 'require("./")("' + base + '/redirect/301", { timeout: 5000 })']) + .on('exit', function () { + done(); + }); + }); + + it('should clear internal timeout on fetch error', function (done) { + this.timeout(1000); + spawn('node', ['-e', 'require("./")("' + base + '/error/reset", { timeout: 5000 })']) + .on('exit', function () { + done(); + }); + }); + it('should allow POST request', function() { url = base + '/inspect'; opts = { @@ -583,12 +620,4 @@ describe('node-fetch', function() { expect(res.ok).to.be.true; }); }); - - it('should remove timeout on response', function (done) { - this.timeout(1e3); - spawn('node', ['-e', 'require("./")("' + base + '/hello", {timeout: 1e4})']) - .on('exit', function () { - done(); - }); - }); });