diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..4872c5a --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: +- package-ecosystem: npm + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 diff --git a/README.md b/README.md index e0d2d3e..d9b9874 100644 --- a/README.md +++ b/README.md @@ -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: @@ -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); diff --git a/examples/client.js b/examples/client.js index 0481c7c..0cd4ea1 100644 --- a/examples/client.js +++ b/examples/client.js @@ -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. */ diff --git a/examples/server.js b/examples/server.js index bee3090..25e0483 100644 --- a/examples/server.js +++ b/examples/server.js @@ -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); + diff --git a/package.json b/package.json index 1608f12..03ced4f 100644 --- a/package.json +++ b/package.json @@ -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": [ diff --git a/src/jsonrpc.js b/src/jsonrpc.js index 23ef0f4..7e59d56 100644 --- a/src/jsonrpc.js +++ b/src/jsonrpc.js @@ -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 //===----------------------------------------------------------------------===// @@ -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 @@ -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); @@ -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); + } + } }); }); }; @@ -137,17 +143,6 @@ 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 //===----------------------------------------------------------------------===// @@ -155,42 +150,42 @@ 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); @@ -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); } @@ -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) @@ -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(); } diff --git a/test/jsonrpc-test.js b/test/jsonrpc-test.js index fbab78e..2bfb4fa 100644 --- a/test/jsonrpc-test.js +++ b/test/jsonrpc-test.js @@ -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); })