Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
version: 2
updates:
- package-ecosystem: npm
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 10
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ To install node-jsonrpc2 in the current directory, run:

npm install jsonrpc2

**Important**: If you run this command, you will get not this repository, but [bitconjs repository](https://github.com/bitcoinjs/node-jsonrpc2).
## Usage

Firing up an efficient JSON-RPC server becomes extremely simple:
Expand All @@ -19,7 +20,7 @@ var rpc = require('jsonrpc2');
var server = new rpc.Server();

function add(args, opt, callback) {
callback(null, args[0] + args[1]);
callback(args[0] + args[1]);
}
server.expose('add', add);

Expand Down
13 changes: 11 additions & 2 deletions examples/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,21 @@ client.call('math.power', [3, 3], function (err, result) {
});

// We can handle errors the same way as anywhere else in Node
client.call('add', [1, 1], function (err, result) {
client.call('wrong', [1, 1], function (err, result) {
if (err) {
sys.puts('RPC Error: '+ sys.inspect(err));
return;
}
sys.puts(' 1 + 1 = ' + result + ', dummy!');
sys.puts(result);
});

// If you want to seperate the errors from your callback,
// then define an extra error callback
client.call('wrong', [1, 1], function (err, result) {
sys.puts(result);
},
function(err){
sys.puts('RPC Error: ' + sys.inspect(err));
});

/* These calls should each take 1.5 seconds to complete. */
Expand Down
21 changes: 15 additions & 6 deletions examples/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ var server = new rpc.Server();

/* Create two simple functions */
function add(args, opts, callback) {
callback(null, args[0]+args[1]);
callback(args[0]+args[1]);
}

function multiply(args, opts, callback) {
callback(null, args[0]*args[1]);
callback(args[0]*args[1]);
}

/* Expose those methods */
Expand All @@ -18,10 +18,10 @@ server.expose('multiply', multiply);
/* We can expose entire modules easily */
var math = {
power: function(args, opts, callback) {
callback(null, Math.pow(args[0], args[1]));
callback(Math.pow(args[0], args[1]));
},
sqrt: function(args, opts, callback) {
callback(null, Math.sqrt(args[0]));
callback(Math.sqrt(args[0]));
}
}
server.exposeModule('math', math);
Expand All @@ -36,7 +36,7 @@ var delayed = {
var data = args[0];
var delay = args[1];
setTimeout(function() {
callback(null, data);
callback(data);
}, delay);
},

Expand All @@ -45,9 +45,18 @@ var delayed = {
var second = args[1];
var delay = args[2];
setTimeout(function() {
callback(null, first + second);
callback(first + second);
}, delay);
}
}

server.exposeModule('delayed', delayed);


// We can also add error parameters to our callback
// if something went wrong
function wrong(arg, opts, callback) {
callback(null, "This will ever go wrong.")
}
server.expose('wrong', wrong);

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "jsonrpc2",
"version": "0.0.6",
"version": "0.0.8",
"description": "JSON-RPC server and client library",
"main": "./src/jsonrpc",
"keywords": [
Expand Down
125 changes: 77 additions & 48 deletions src/jsonrpc.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
var sys = require('sys');
var http = require('http');

var METHOD_NOT_ALLOWED = "Method Not Allowed\n";
var INVALID_REQUEST = "Invalid Request\n";


//===----------------------------------------------------------------------===//
// Server Client
//===----------------------------------------------------------------------===//
Expand All @@ -19,6 +15,7 @@ var Client = function(port, host, user, password) {

// First we encode the request into JSON
var requestJSON = JSON.stringify({
'jsonrpc': '2.0',
'id': '' + (new Date()).getTime(),
'method': method,
'params': params
Expand All @@ -37,6 +34,11 @@ var Client = function(port, host, user, password) {
headers['Host'] = host;
headers['Content-Length'] = requestJSON.length;

// Report errors from the http client. This also prevents crashes since an exception is thrown if we don't handle this event.
client.on('error', function(err) {
callback(err);
});

// Now we'll make a request to the server
var request = client.request('POST', path || '/', headers);
request.write(requestJSON);
Expand All @@ -50,15 +52,19 @@ var Client = function(port, host, user, password) {
// depending on whether it's got a result or an error, we call
// emitSuccess or emitError on the promise.
response.on('end', function() {
var decoded = JSON.parse(buffer);
var decoded = JSON.parse(buffer); // TODO: Check for invalid response from server
if(decoded.hasOwnProperty('result')) {
if (callback)
if (callback)
callback(null, decoded.result);
}
else {
if (errback)
errback(decoded.error);
}

} else {
// Call error handler if it is set, otherwise call callback with error parameters
if (errback) {
errback(decoded.error);
} else if(callback) {
callback(decoded.error, null);
}
}
});
});
};
Expand Down Expand Up @@ -137,60 +143,49 @@ Server.prototype.listen = function(port, host) {
}


//===----------------------------------------------------------------------===//
// handleInvalidRequest
//===----------------------------------------------------------------------===//
Server.handleInvalidRequest = function(req, res) {
res.writeHead(400, {'Content-Type': 'text/plain',
'Content-Length': INVALID_REQUEST.length});
res.write(INVALID_REQUEST);
res.end();
}


//===----------------------------------------------------------------------===//
// handlePOST
//===----------------------------------------------------------------------===//
Server.prototype.handlePOST = function(req, res) {
var buffer = '';
var self = this;
var handle = function (buf) {
var decoded = JSON.parse(buf);

var decoded = "";
try {
decoded = JSON.parse(buf);
} catch (e) {
return Server.handleError(-32700, "Parse Error", null, req, res);
}


// Check for the required fields, and if they aren't there, then
// dispatch to the handleInvalidRequest function.
// dispatch to the handleError function.
if(!(decoded.method && decoded.params && decoded.id)) {
return Server.handleInvalidRequest(req, res);

if (typeof(id) == "undefined") {
var id = null;
}

return Server.handleError(-32600, "Invalid Request", decoded.id, req, res);
}

if(!self.functions.hasOwnProperty(decoded.method)) {
return Server.handleInvalidRequest(req, res);
return Server.handleError(-32601, "Method not found", decoded.id, req, res);
}

// Build our success handler
var onSuccess = function(funcResp) {
Server.trace('-->', 'response (id ' + decoded.id + '): ' +
JSON.stringify(funcResp));

var encoded = JSON.stringify({

var encoded = JSON.stringify({
'jsonrpc': '2.0',
'result': funcResp,
'error': null,
'id': decoded.id
});
res.writeHead(200, {'Content-Type': 'application/json',
'Content-Length': encoded.length});
res.write(encoded);
res.end();
};

// Build our failure handler (note that error must not be null)
var onFailure = function(failure) {
Server.trace('-->', 'failure: ' + JSON.stringify(failure));
var encoded = JSON.stringify({
'result': null,
'error': failure || 'Unspecified Failure',
'id': decoded.id
});

res.writeHead(200, {'Content-Type': 'application/json',
'Content-Length': encoded.length});
res.write(encoded);
Expand All @@ -203,9 +198,9 @@ Server.prototype.handlePOST = function(req, res) {
// Try to call the method, but intercept errors and call our
// onFailure handler.
var method = self.functions[decoded.method];
var callback = function(err, result) {
if (err) {
onFailure(err);
var callback = function(result, errormessage) {
if (errormessage) {
Server.handleError(-32602, errormessage, decoded.id, req, res);
} else {
onSuccess(result);
}
Expand All @@ -222,7 +217,7 @@ Server.prototype.handlePOST = function(req, res) {
try {
method.call(scope, decoded.params, opt, callback);
} catch (err) {
return onFailure(err);
return Server.handleError(-32603, err, decoded.id, req, res);
}

} // function handle(buf)
Expand All @@ -236,15 +231,49 @@ Server.prototype.handlePOST = function(req, res) {
});
}

//===----------------------------------------------------------------------===//
// handleError
//===----------------------------------------------------------------------===//
Server.handleError = function(code, message, id, req, res) {

var encoded = JSON.stringify({
'jsonrpc': '2.0',
'error': {
'code':code,
'message':message
},
'id': id
});

res.writeHead(400, {'Content-Type': 'text/plain',
'Content-Length': encoded.length,
'Allow': 'POST'});

res.write(encoded);
res.end();

Server.trace('-->', 'Failure: ' + code + ': ' + message);
}


//===----------------------------------------------------------------------===//
// handleNonPOST
//===----------------------------------------------------------------------===//
Server.handleNonPOST = function(req, res) {

var encoded = JSON.stringify({
'jsonrpc': '2.0',
'error': {
'code':-32600,
'message':"Only POST is allowed."
},
'id': null
});

res.writeHead(405, {'Content-Type': 'text/plain',
'Content-Length': METHOD_NOT_ALLOWED.length,
'Content-Length': encoded.length,
'Allow': 'POST'});
res.write(METHOD_NOT_ALLOWED);
res.write(encoded);
res.end();
}

Expand Down
13 changes: 6 additions & 7 deletions test/jsonrpc-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ var TestModule = {

test('Server.expose', function() {
var echo = function(args, opts, callback) {
callback(null, args[0]);
callback(args[0], null);
};
server.expose('echo', echo);
assert(server.functions.echo === echo);
Expand Down Expand Up @@ -66,7 +66,6 @@ function testBadRequest(testJSON) {
server.handlePOST(req, res);
req.emit('data', testJSON);
req.emit('end');
sys.puts(res.httpCode);
assert(res.httpCode === 400);
}

Expand Down Expand Up @@ -99,6 +98,7 @@ test('Simple synchronous echo', function() {
server.handlePOST(req, res);
req.emit('data', testJSON);
req.emit('end');

assert(res.httpCode === 200);
var decoded = JSON.parse(res.httpBody);
assert(decoded.id === 1);
Expand Down Expand Up @@ -126,7 +126,7 @@ test('Using promise', function() {
// yet.
assert(res['httpCode'] == null);
// We can force the promise to emit a success code, with a message.
callbackRef(null, 'Hello, World!');
callbackRef('Hello, World!', null);
// Aha, now that the promise has finished, our request has finished as well.
assert(res.httpCode === 200);
var decoded = JSON.parse(res.httpBody);
Expand All @@ -146,13 +146,12 @@ test('Triggering an errback', function() {
server.handlePOST(req, res);
req.emit('data', testJSON);
req.emit('end');
assert(res['httpCode'] == null);
// This time, unlike the above test, we trigger an error and expect to see
// it in the error attribute of the object returned.
callbackRef('This is an error');
assert(res.httpCode === 200);
callbackRef(null, 'This is an error');
assert(res.httpCode === 400);
var decoded = JSON.parse(res.httpBody);
assert(decoded.id === 1);
assert(decoded.error == 'This is an error');
assert(decoded.error.message == 'This is an error');
assert(decoded.result == null);
})