RPC: Refactor, documentation and tests
This commit is contained in:
parent
4173f4b49e
commit
d7b8b370f1
@ -2,7 +2,8 @@
|
|||||||
* @namespace Transport
|
* @namespace Transport
|
||||||
*/
|
*/
|
||||||
module.exports = {
|
module.exports = {
|
||||||
Peer: require('./peer'),
|
|
||||||
Messages: require('./messages'),
|
Messages: require('./messages'),
|
||||||
Pool: require('./pool')
|
Peer: require('./peer'),
|
||||||
|
Pool: require('./pool'),
|
||||||
|
RPC: require('./rpc')
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,30 +1,118 @@
|
|||||||
// RpcClient.js
|
'use strict';
|
||||||
// MIT/X11-like license. See LICENSE.txt.
|
|
||||||
// Copyright 2013 BitPay, Inc.
|
|
||||||
//
|
|
||||||
var http = require('http');
|
var http = require('http');
|
||||||
var https = require('https');
|
var https = require('https');
|
||||||
var log = require('../util/log');
|
|
||||||
|
|
||||||
function RpcClient(opts) {
|
/**
|
||||||
|
* A JSON RPC client for bitcoind. An instances of RPC connects to a bitcoind
|
||||||
|
* server and enables simple and batch RPC calls.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* var client = new RPC('user', 'pass');
|
||||||
|
* client.getInfo(function(err, info) {
|
||||||
|
* // do something with the info
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* @param {String} user - username used to connect bitcoind
|
||||||
|
* @param {String} password - password used to connect bitcoind
|
||||||
|
* @param {Object} opts - Connection options: host, port, secure, disableAgent, rejectUnauthorized
|
||||||
|
* @returns {RPC}
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function RPC(user, password, opts) {
|
||||||
|
if (!(this instanceof RPC)) {
|
||||||
|
return new RPC(user, password, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.user = user;
|
||||||
|
this.pass = password;
|
||||||
|
|
||||||
opts = opts || {};
|
opts = opts || {};
|
||||||
this.host = opts.host || '127.0.0.1';
|
this.host = opts.host || '127.0.0.1';
|
||||||
this.port = opts.port || 8332;
|
this.port = opts.port || 8332;
|
||||||
this.user = opts.user || 'user';
|
|
||||||
this.pass = opts.pass || 'pass';
|
this.secure = typeof opts.secure === 'undefined' ? true : opts.secure;
|
||||||
this.protocol = (opts.protocol == 'http') ? http : https;
|
this._client = opts.secure ? https : http;
|
||||||
|
|
||||||
this.batchedCalls = null;
|
this.batchedCalls = null;
|
||||||
this.disableAgent = opts.disableAgent || false;
|
this.disableAgent = opts.disableAgent || false;
|
||||||
this.rejectUnauthorized = opts.rejectUnauthorized || false;
|
this.rejectUnauthorized = opts.rejectUnauthorized || false;
|
||||||
}
|
}
|
||||||
|
|
||||||
RpcClient.prototype.batch = function(batchCallback, resultCallback) {
|
/**
|
||||||
|
* Allows to excecute RPC calls in batch.
|
||||||
|
*
|
||||||
|
* @param {Function} batchCallback - Function that makes all calls to be excecuted in bach
|
||||||
|
* @param {Function} resultCallbak - Function to be called on result
|
||||||
|
*/
|
||||||
|
RPC.prototype.batch = function(batchCallback, resultCallback) {
|
||||||
this.batchedCalls = [];
|
this.batchedCalls = [];
|
||||||
batchCallback();
|
batchCallback();
|
||||||
rpc.call(this, this.batchedCalls, resultCallback);
|
this._request(this.batchedCalls, resultCallback);
|
||||||
this.batchedCalls = null;
|
this.batchedCalls = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal function to make an RPC call
|
||||||
|
*
|
||||||
|
* @param {Object} request - Object to be serialized and sent to bitcoind
|
||||||
|
* @param {Function} callbak - Function to be called on result
|
||||||
|
*/
|
||||||
|
RPC.prototype._request = function(request, callback) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
var request = JSON.stringify(request);
|
||||||
|
var auth = Buffer(self.user + ':' + self.pass).toString('base64');
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
host: self.host,
|
||||||
|
path: '/',
|
||||||
|
method: 'POST',
|
||||||
|
port: self.port,
|
||||||
|
rejectUnauthorized: self.rejectUnauthorized,
|
||||||
|
agent: self.disableAgent ? false : undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
var req = this._client.request(options, function(res) {
|
||||||
|
var buf = '';
|
||||||
|
res.on('data', function(data) {
|
||||||
|
buf += data;
|
||||||
|
});
|
||||||
|
|
||||||
|
res.on('end', function() {
|
||||||
|
if (res.statusCode == 401) {
|
||||||
|
var error = new Error('bitcoin JSON-RPC connection rejected: 401 unauthorized');
|
||||||
|
return callback(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.statusCode == 403) {
|
||||||
|
var error = new Error('bitcoin JSON-RPC connection rejected: 403 forbidden');
|
||||||
|
return callback(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
var parsedBuf = JSON.parse(buf);
|
||||||
|
} catch (e) {
|
||||||
|
return callback(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(parsedBuf.error, parsedBuf);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
req.on('error', function(e) {
|
||||||
|
var err = new Error('Could not connect to bitcoin via RPC at host: ' + self.host + ' port: ' + self.port + ' Error: ' + e.message);
|
||||||
|
callback(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
req.setHeader('Content-Length', request.length);
|
||||||
|
req.setHeader('Content-Type', 'application/json');
|
||||||
|
req.setHeader('Authorization', 'Basic ' + auth);
|
||||||
|
req.write(request);
|
||||||
|
req.end();
|
||||||
|
};
|
||||||
|
|
||||||
var callspec = {
|
var callspec = {
|
||||||
addMultiSigAddress: '',
|
addMultiSigAddress: '',
|
||||||
addNode: '',
|
addNode: '',
|
||||||
@ -94,11 +182,12 @@ var callspec = {
|
|||||||
walletPassphraseChange: '',
|
walletPassphraseChange: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
var slice = function(arr, start, end) {
|
var slice = function(arr, start, end) {
|
||||||
return Array.prototype.slice.call(arr, start, end);
|
return Array.prototype.slice.call(arr, start, end);
|
||||||
};
|
};
|
||||||
|
|
||||||
function generateRPCMethods(constructor, apiCalls, rpc) {
|
function generateRPCMethods(constructor, apiCalls) {
|
||||||
function createRPCMethod(methodName, argMap) {
|
function createRPCMethod(methodName, argMap) {
|
||||||
return function() {
|
return function() {
|
||||||
var limit = arguments.length - 1;
|
var limit = arguments.length - 1;
|
||||||
@ -113,7 +202,7 @@ function generateRPCMethods(constructor, apiCalls, rpc) {
|
|||||||
params: slice(arguments)
|
params: slice(arguments)
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
rpc.call(this, {
|
this._request({
|
||||||
method: methodName,
|
method: methodName,
|
||||||
params: slice(arguments, 0, arguments.length - 1)
|
params: slice(arguments, 0, arguments.length - 1)
|
||||||
}, arguments[arguments.length - 1]);
|
}, arguments[arguments.length - 1]);
|
||||||
@ -153,71 +242,6 @@ function generateRPCMethods(constructor, apiCalls, rpc) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function rpc(request, callback) {
|
generateRPCMethods(RPC, callspec);
|
||||||
var self = this;
|
|
||||||
var request;
|
|
||||||
request = JSON.stringify(request);
|
|
||||||
var auth = Buffer(self.user + ':' + self.pass).toString('base64');
|
|
||||||
|
|
||||||
var options = {
|
module.exports = RPC;
|
||||||
host: self.host,
|
|
||||||
path: '/',
|
|
||||||
method: 'POST',
|
|
||||||
port: self.port,
|
|
||||||
rejectUnauthorized: self.rejectUnauthorized,
|
|
||||||
agent: self.disableAgent ? false : undefined,
|
|
||||||
};
|
|
||||||
if (self.httpOptions) {
|
|
||||||
for (var k in self.httpOptions) {
|
|
||||||
options[k] = self.httpOptions[k];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var err = null;
|
|
||||||
var req = this.protocol.request(options, function(res) {
|
|
||||||
|
|
||||||
var buf = '';
|
|
||||||
res.on('data', function(data) {
|
|
||||||
buf += data;
|
|
||||||
});
|
|
||||||
res.on('end', function() {
|
|
||||||
if (res.statusCode == 401) {
|
|
||||||
callback(new Error('bitcoin JSON-RPC connection rejected: 401 unauthorized'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (res.statusCode == 403) {
|
|
||||||
callback(new Error('bitcoin JSON-RPC connection rejected: 403 forbidden'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err) {
|
|
||||||
callback(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
var parsedBuf = JSON.parse(buf);
|
|
||||||
} catch (e) {
|
|
||||||
log.err(e.stack);
|
|
||||||
log.err(buf);
|
|
||||||
log.err('HTTP Status code:' + res.statusCode);
|
|
||||||
callback(e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
callback(parsedBuf.error, parsedBuf);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
req.on('error', function(e) {
|
|
||||||
var err = new Error('Could not connect to bitcoin via RPC at host: ' + self.host + ' port: ' + self.port + ' Error: ' + e.message);
|
|
||||||
log.err(err);
|
|
||||||
callback(err);
|
|
||||||
});
|
|
||||||
|
|
||||||
req.setHeader('Content-Length', request.length);
|
|
||||||
req.setHeader('Content-Type', 'application/json');
|
|
||||||
req.setHeader('Authorization', 'Basic ' + auth);
|
|
||||||
req.write(request);
|
|
||||||
req.end();
|
|
||||||
};
|
|
||||||
|
|
||||||
generateRPCMethods(RpcClient, callspec, rpc);
|
|
||||||
|
|
||||||
module.exports = RpcClient;
|
|
||||||
|
|||||||
@ -1,28 +1,62 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var chai = chai || require('chai');
|
var chai = require('chai');
|
||||||
var bitcore = bitcore || require('../bitcore');
|
|
||||||
|
|
||||||
var should = chai.should();
|
var should = chai.should();
|
||||||
|
|
||||||
var RpcClientModule = bitcore.RpcClient;
|
var bitcore = require('../..');
|
||||||
var RpcClient;
|
var RPC = bitcore.transport.RPC;
|
||||||
RpcClient = RpcClientModule;
|
|
||||||
|
|
||||||
describe('RpcClient', function() {
|
describe('RPC', function() {
|
||||||
it('should initialze the main object', function() {
|
|
||||||
should.exist(RpcClientModule);
|
|
||||||
});
|
|
||||||
it('should be able to create class', function() {
|
|
||||||
should.exist(RpcClient);
|
|
||||||
});
|
|
||||||
it('should be able to create instance', function() {
|
it('should be able to create instance', function() {
|
||||||
var s = new RpcClient();
|
var client = new RPC('user', 'pass');
|
||||||
should.exist(s);
|
should.exist(client);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should set default config', function() {
|
||||||
|
var client = new RPC('user', 'pass');
|
||||||
|
client.user.should.be.equal('user');
|
||||||
|
client.pass.should.be.equal('pass');
|
||||||
|
|
||||||
|
client.host.should.be.equal('127.0.0.1');
|
||||||
|
client.port.should.be.equal(8332);
|
||||||
|
client.secure.should.be.equal(true);
|
||||||
|
client.disableAgent.should.be.equal(false);
|
||||||
|
client.rejectUnauthorized.should.be.equal(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow setting custom host and port', function() {
|
||||||
|
var client = new RPC('user', 'pass', {
|
||||||
|
host: 'localhost',
|
||||||
|
port: 18332
|
||||||
|
});
|
||||||
|
|
||||||
|
client.host.should.be.equal('localhost');
|
||||||
|
client.port.should.be.equal(18332);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should honor request options', function() {
|
||||||
|
var client = new RPC('user', 'pass', {
|
||||||
|
host: 'localhost',
|
||||||
|
port: 18332,
|
||||||
|
rejectUnauthorized: true,
|
||||||
|
disableAgent: true
|
||||||
|
});
|
||||||
|
|
||||||
|
client._client = {};
|
||||||
|
client._client.request = function(options, callback) {
|
||||||
|
options.host.should.be.equal('localhost');
|
||||||
|
options.port.should.be.equal(18332);
|
||||||
|
options.rejectUnauthorized.should.be.equal(true);
|
||||||
|
options.agent.should.be.false;
|
||||||
|
return {
|
||||||
|
on: function() {},
|
||||||
|
setHeader: function() {},
|
||||||
|
write: function() {},
|
||||||
|
end: function() {}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
client._request({}, function() {});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user