Permalink
Browse files

Added true JSON-RPC compatibility & improved error handling

* Removed handleInvalidRequest()
* Added handleError with JSON error object
* Now returning JSON-RPC as it is in the specs
(http://groups.google.com/group/json-rpc/web/json-rpc-2-0?pli=1)
* Prevented server crashes because of invalid JSON
* Changed callback syntax used by the server functions (look at README)
* Allowing extra error callback or error parameter in
success callback at the client
  • Loading branch information...
1 parent faa0064 commit 9fb844cd8dc896ab0c449f550a53df737187c704 @Philipp15b committed Aug 18, 2011
Showing with 103 additions and 65 deletions.
  1. +1 −1 README.md
  2. +11 −2 examples/client.js
  3. +15 −6 examples/server.js
  4. +70 −49 src/jsonrpc.js
  5. +6 −7 test/jsonrpc-test.js
View
@@ -19,7 +19,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);
View
@@ -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. */
View
@@ -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 */
@@ -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);
@@ -36,7 +36,7 @@ var delayed = {
var data = args[0];
var delay = args[1];
setTimeout(function() {
- callback(null, data);
+ callback(data);
}, delay);
},
@@ -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);
+
View
@@ -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
//===----------------------------------------------------------------------===//
@@ -51,15 +47,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);
+ }
+ }
});
});
};
@@ -138,62 +138,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({
- 'jsonrpc': '2.0',
- 'result': null,
- 'error': failure || 'Unspecified Failure',
- 'id': decoded.id
- });
+
res.writeHead(200, {'Content-Type': 'application/json',
'Content-Length': encoded.length});
res.write(encoded);
@@ -206,9 +193,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);
}
@@ -225,7 +212,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)
@@ -239,15 +226,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();
}
View
@@ -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);
@@ -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);
}
@@ -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);
@@ -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);
@@ -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);
})

0 comments on commit 9fb844c

Please sign in to comment.