Browse Source

RPC: Refactor, documentation and tests

patch-2
Yemel Jardi 10 years ago
parent
commit
d7b8b370f1
  1. 5
      lib/transport/index.js
  2. 184
      lib/transport/rpc.js
  3. 66
      test/transport/rpc.js

5
lib/transport/index.js

@ -2,7 +2,8 @@
* @namespace Transport
*/
module.exports = {
Peer: require('./peer'),
Messages: require('./messages'),
Pool: require('./pool')
Peer: require('./peer'),
Pool: require('./pool'),
RPC: require('./rpc')
};

184
lib/transport/rpc.js

@ -1,30 +1,118 @@
// RpcClient.js
// MIT/X11-like license. See LICENSE.txt.
// Copyright 2013 BitPay, Inc.
//
'use strict';
var http = require('http');
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 || {};
this.host = opts.host || '127.0.0.1';
this.port = opts.port || 8332;
this.user = opts.user || 'user';
this.pass = opts.pass || 'pass';
this.protocol = (opts.protocol == 'http') ? http : https;
this.secure = typeof opts.secure === 'undefined' ? true : opts.secure;
this._client = opts.secure ? https : http;
this.batchedCalls = null;
this.disableAgent = opts.disableAgent || 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 = [];
batchCallback();
rpc.call(this, this.batchedCalls, resultCallback);
this._request(this.batchedCalls, resultCallback);
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 = {
addMultiSigAddress: '',
addNode: '',
@ -94,11 +182,12 @@ var callspec = {
walletPassphraseChange: '',
};
var slice = function(arr, start, end) {
return Array.prototype.slice.call(arr, start, end);
};
function generateRPCMethods(constructor, apiCalls, rpc) {
function generateRPCMethods(constructor, apiCalls) {
function createRPCMethod(methodName, argMap) {
return function() {
var limit = arguments.length - 1;
@ -113,7 +202,7 @@ function generateRPCMethods(constructor, apiCalls, rpc) {
params: slice(arguments)
});
} else {
rpc.call(this, {
this._request({
method: methodName,
params: slice(arguments, 0, arguments.length - 1)
}, arguments[arguments.length - 1]);
@ -153,71 +242,6 @@ function generateRPCMethods(constructor, apiCalls, rpc) {
}
}
function rpc(request, callback) {
var self = this;
var request;
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,
};
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);
generateRPCMethods(RPC, callspec);
module.exports = RpcClient;
module.exports = RPC;

66
test/transport/rpc.js

@ -1,28 +1,62 @@
'use strict';
var chai = chai || require('chai');
var bitcore = bitcore || require('../bitcore');
var chai = require('chai');
var should = chai.should();
var RpcClientModule = bitcore.RpcClient;
var RpcClient;
RpcClient = RpcClientModule;
var bitcore = require('../..');
var RPC = bitcore.transport.RPC;
describe('RpcClient', function() {
it('should initialze the main object', function() {
should.exist(RpcClientModule);
});
it('should be able to create class', function() {
should.exist(RpcClient);
});
describe('RPC', function() {
it('should be able to create instance', function() {
var s = new RpcClient();
should.exist(s);
var client = new RPC('user', 'pass');
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…
Cancel
Save