diff --git a/lib/jsonrpc.js b/lib/jsonrpc.js index 9d40b0c..1573b6d 100644 --- a/lib/jsonrpc.js +++ b/lib/jsonrpc.js @@ -54,13 +54,41 @@ Client.prototype.call = function(method, params, callback, errback, path) { if (this.opts.user && this.opts.pass) { requestOptions.auth = this.opts.user + ':' + this.opts.pass; } - + // Now we'll make a request to the server + var cbCalled = false; var request = this.http.request(requestOptions); + + // start request timeout timer + var reqTimeout = setTimeout(function() { + if (cbCalled) return; + cbCalled = true; + request.abort(); + var err = new Error('ETIMEDOUT'); + err.code = 'ETIMEDOUT'; + errback(err); + }, this.opts.timeout || 5000); + + // set additional timeout on socket in case of remote freeze after sending headers + request.setTimeout(this.opts.timeout || 5000, function() { + if (cbCalled) return; + cbCalled = true; + request.abort(); + var err = new Error('ESOCKETTIMEDOUT') + err.code = 'ESOCKETTIMEDOUT' + errback(err); + }); - request.on('error', errback); + request.on('error', function(err) { + if (cbCalled) return; + cbCalled = true; + clearTimeout(reqTimeout); + errback(err); + }); request.on('response', function(response) { + clearTimeout(reqTimeout); + // We need to buffer the response chunks in a nonblocking way. var buffer = ''; response.on('data', function(chunk) { @@ -72,6 +100,9 @@ Client.prototype.call = function(method, params, callback, errback, path) { response.on('end', function() { var err; + if (cbCalled) return; + cbCalled = true; + try { var decoded = JSON.parse(buffer); } catch (e) { diff --git a/test/index.js b/test/index.js index cc06400..0eb40d3 100644 --- a/test/index.js +++ b/test/index.js @@ -1,5 +1,6 @@ var assert = require('assert'), clone = require('clone'), + http = require('http'), bitcoin = require('../'), config = require('./config'); @@ -16,6 +17,12 @@ var notEmpty = function notEmpty(data) { assert.ok(data); }; +var makeServer = function(port) { + var server = http.createServer(); + server.listen(port); + return server; +}; + describe('Client', function() { describe('getAccountAddress()', function() { @@ -209,4 +216,62 @@ describe('Client', function() { }); }); + describe('request timeouts', function() { + it('should occur by default after 5000ms', function(done) { + this.timeout(7500); + var server = makeServer(19998); + var request; + var response; + server.on('request', function(req, res) { + request = req; + response = res; + }); + var client = new bitcoin.Client({ + host: 'localhost', + port: 19998, + user: 'admin1', + pass: '123' + }); + var start = Date.now(); + client.getInfo(function(err, info) { + var delta = Date.now() - start; + assert.ok(err instanceof Error); + assert.ok(err.code === 'ETIMEDOUT' || err.code === 'ESOCKETTIMEDOUT'); + assert.ok(delta >= 5000, 'delta should be >= 5000: ' + delta); + response.end(); + server.close(); + done(); + }); + }); + + it('should be customizable', function(done) { + this.timeout(4500); + var server = makeServer(19999); + var request; + var response; + server.on('request', function(req, res) { + request = req; + response = res; + }); + var client = new bitcoin.Client({ + host: 'localhost', + port: 19999, + user: 'admin1', + pass: '123', + timeout: 2500 + }); + var start = Date.now(); + client.getInfo(function(err, info) { + var delta = Date.now() - start; + assert.ok(err instanceof Error); + assert.ok(err.code === 'ETIMEDOUT' || err.code === 'ESOCKETTIMEDOUT'); + assert.ok(delta >= 2500, 'delta should be >= 2500:' + delta); + response.end(); + server.close(); + done(); + }); + }); + + }); + });