Socket
Socket
Sign inDemoInstall

jayson

Package Overview
Dependencies
6
Maintainers
1
Versions
54
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

    jayson

JSON-RPC 1.0/2.0 compliant server and client


Version published
Maintainers
1
Install size
2.36 MB
Created

Readme

Source

Jayson

Jayson is a JSON-RPC 2.0 and 1.0 compliant server and client written in JavaScript for node.js that aims to be as simple as possible to use.

travis build status npm version npm

Table of contents

Features

Example

A basic JSON-RPC 2.0 server via HTTP:

Server example in examples/simple_example/server.js:

var jayson = require('jayson');

// create a server
var server = jayson.server({
  add: function(args, callback) {
    callback(null, args[0] + args[1]);
  }
});

server.http().listen(3000);

Client example in examples/simple_example/client.js invoking add on the above server:

var jayson = require('jayson');

// create a client
var client = jayson.client.http({
  port: 3000
});

// invoke "add"
client.request('add', [1, 1], function(err, response) {
  if(err) throw err;
  console.log(response.result); // 2
});

Installation

Install the latest version of jayson from npm by executing npm install jayson in your shell. Do a global install with npm install --global jayson if you want the jayson client CLI in your PATH.

Changelog (only notable milestones)

  • 2.0.0
    • Added support for promises
    • Breaking: collect: true is now the default option for a new jayson.Server and jayson.Method
  • 1.2.0
  • 1.1.1
    • More http server events
    • Remove fork server and client
    • Add server routing
  • 1.0.11 Add support for a HTTPS client
  • 1.0.9 Add support for TCP servers and clients

CLI client

There is a CLI client in bin/jayson.js and it should be available as jayson in your shell if you installed the package globally. Run jayson --help to see how it works.

Requirements

Jayson does not have any special dependencies that cannot be resolved with a simple npm install. It is being continuously tested using travis-ci on the following versions/releases:

  • "node"
  • "iojs"
  • "4"
  • "5"
  • "6"
  • "0.12"

Class documentation

In addition to this document, a comprehensive class documentation made with jsdoc is available at jayson.tedeh.net.

Running tests

  • Change directory to the repository root
  • Install the development packages by executing npm install --dev
  • Run the tests with npm run test

Usage

Client

The client is available as the Client or client property of require('jayson').

Client interface description
NameDescription
ClientBase class
Client.tcpTCP interface
Client.tlsTLS interface
Client.httpHTTP interface
Client.httpsHTTPS interface

Every client supports these options:

OptionDefaultTypeDescription
reviverundefinedFunctionJSON.parse reviver
replacerundefinedFunctionJSON.stringify replacer
generatorRFC4122 generatorFunctionGenerates a String for request ID.
version2NumberJSON-RPC version to support (1 or 2)
Client.http

Uses the same options as http.request in addition to these options:

OptionDefaultTypeDescription
encodingutf8StringDetermines the encoding to use
Client.http Events

The HTTP server will emit the following events:

EventWhenArgumentsNotes
http requestCreated an HTTP request1. Instance of http.ClientRequest
http responseReceived an HTTP response1. Instance of http.IncomingMessage 2. Instance of http.ClientRequest
http errorUnderlying stream emits error1. Error
http timeoutUnderlying stream emits timeoutAutomatically causes the request to abort

It is possible to pass a string URL as the first argument. The URL will be run through url.parse. Example:

var jayson = require('jayson');
var client = jayson.client.http('http://localhost:3000');
// client.options is now the result of url.parse
Client.https

Uses the same options as https.request in addition to the same options as Client.http. This means it is also possible to pass a string URL as the first argument and have it interpreted by url.parse.

Will emit the same custom events as Client.http.

Client.tcp

Uses the same options as net.connect in addition to the same options as Client.http.

Client.tls

Uses the same options as tls.connect in addition to the same options as Client.http.

Notifications

Notification requests are for cases where the reply from the server is not important and should be ignored. This is accomplished by setting the id property of a request object to null.

Client example in examples/notifications/client.js doing a notification request:

var jayson = require('jayson');

var client = jayson.client.http({
  port: 3000
});

// the third parameter is set to "null" to indicate a notification
client.request('ping', [], null, function(err) {
  if(err) throw err;
  console.log('ok'); // request was received successfully
});

Server example in examples/notifications/server.js:

var jayson = require('jayson');

var server = jayson.server({
  ping: function(args, callback) {
    // do something, do nothing
    callback();
  }
});

server.http().listen(3000);
Notes
  • Any value that the server returns will be discarded when doing a notification request.
  • Omitting the third argument null to Client.prototype.request does not generate a notification request. This argument has to be set explicitly to null for this to happen.
  • Network errors and the like will still reach the callback. When the callback is invoked (with or without error) one can be certain that the server has received the request.
  • See the Official JSON-RPC 2.0 Specification for additional information on how Jayson handles notifications that are erroneous.
Batches

A batch request is an array of individual requests that are sent to the server as one. Doing a batch request is very simple in Jayson and consists of constructing an array of individual requests (created by not passing a callback to Client.prototype.request) that is then itself passed to Client.prototype.request.

Combined server/client example in examples/batch_request/index.js:

var jayson = require('jayson');

var server = jayson.server({
  add: function(args, callback) {
    callback(null, args[0] + args[1]);
  }
});

var client = jayson.client(server);

var batch = [
  client.request('does_not_exist', [10, 5]),
  client.request('add', [1, 1]),
  client.request('add', [0, 0], null) // a notification
];

client.request(batch, function(err, errors, successes) {
  if(err) throw err;
  console.log('errors', errors); // array of requests that errored
  console.log('successes', successes); // array of requests that succeeded
});

client.request(batch, function(err, responses) {
  if(err) throw err;
  console.log('responses', responses); // all responses together
});
Notes
  • See the Official JSON-RPC 2.0 Specification for additional information on how Jayson handles different types of batches, mainly with regards to notifications, request errors and so forth.
  • There is no guarantee that the results will be in the same order as request Array request. To find the right result, compare the ID from the request with the ID in the result yourself.
Client callback syntactic sugar

When the length (number of arguments) of a client callback function is either 2 or 3 it receives slightly different values when invoked.

  • 2 arguments: first argument is an error or null, second argument is the response object as returned (containing either a result or a error property) or null for notifications.
  • 3 arguments: first argument is an error or null, second argument is a JSON-RPC error property or null (if success), third argument is a JSON-RPC result property or null (if error).

When doing a batch request with a 3-length callback, the second argument will be an array of requests with a error property and the third argument will be an array of requests with a result property.

Client events

A client will emit the following events (in addition to any special ones emitted by a specific interface):

EventWhenArgumentsNotes
requestAbout to dispatch a request1: Request object
responseReceived a response1: Request object 2: Response object received

Server

The server classes are available as the Server or server property of require('jayson').

The server also sports several interfaces that can be accessed as properties of an instance of Server.

Server interface description
NameDescription
ServerBase interface for a server that supports receiving JSON-RPC requests
Server.tcpTCP server that inherits from net.Server
Server.tlsTLS server that inherits from tls.Server
Server.httpHTTP server that inherits from http.Server
Server.httpsHTTPS server that inherits from https.Server
Server.middlewareMethod that returns a Connect/Express compatible middleware function

Servers supports these options:

OptionDefaultTypeDescription
revivernullFunctionJSON.parse reviver
replacernull FunctionJSON.stringify replacer
routernull FunctionReturn the function for method routing
collecttrueBooleanPassed to methodConstructor options
paramsundefined`ArrayObject
methodConstructorjayson.MethodFunctionServer functions are made an instance of this class
version2NumberJSON-RPC version to support (1 or 2)
Server.tcp

Uses the same options as the base class. Inherits from net.Server.

Server.tls

Uses the same options as the base class. Inherits from tls.Server.

Server.http

Uses the same options as the base class. Inherits from http.Server.

Server.http Events
EventWhenArgumentsNotes
http requestIncoming HTTP request1. Instance of http.IncomingMessage
http responseAbout to send a HTTP response1. Instance of http.ServerResponse 2. Instance of http. IncomingMessage
Server.https

Uses the same options as the base class. Inherits from https.Server and jayson.Server.http. For information on how to configure certificates, see the documentation on https.Server.

Will emit the same custom events as Server.http.

Server.middleware

Uses the same options as the base class. Returns a function that is compatible with Connect or Express. Will expect the request to be req.body, meaning that the request body must be parsed (typically using connect.bodyParser) before the middleware is invoked.

The middleware supports the following options:

OptionDefaultTypeDescription
endtrueBooleanIf set to false causes the middleware to next() instead of res.end() when finished.

Middleware example in examples/middleware/server.js:

var jayson = require('jayson');
var jsonParser = require('body-parser').json;
var connect = require('connect');
var app = connect();

var server = jayson.server({
  add: function(args, callback) {
    callback(null, args[0] + args[1]);
  }
});

// parse request body before the jayson middleware
app.use(jsonParser());
app.use(server.middleware());

app.listen(3000);
Many interfaces at the same time

A Jayson server can use many interfaces at the same time.

Server example in examples/many_interfaces/server.js that listens to both http and a https requests:

var jayson = require('jayson');

var server = jayson.server();

// "http" will be an instance of require('http').Server
var http = server.http();

// "https" will be an instance of require('https').Server
var https = server.https({
  //cert: require('fs').readFileSync('cert.pem'),
  //key require('fs').readFileSync('key.pem')
});

http.listen(80, function() {
  console.log('Listening on *:80');
});

https.listen(443, function() {
  console.log('Listening on *:443');
});
Using the server as a relay

Passing an instance of a client as a method to the server makes the server relay incoming requests to wherever the client is pointing to. This might be used to delegate computationally expensive functions into a separate server or to abstract a cluster of servers behind a common interface.

Frontend server example in examples/relay/server_public.js listening on *:3000:

var jayson = require('jayson');

// create a server where "add" will relay a localhost-only server
var server = jayson.server({
  add: jayson.client.http({
    port: 3001
  })
});

// let the frontend server listen to *:3000
server.http().listen(3000);

Backend server example in examples/relay/server_private.js listening on *:3001:

var jayson = require('jayson');

var server = jayson.server({
  add: function(args, callback) {
    callback(null, args[0] + args[1]);
  }
});

// let the backend listen to *:3001
server.http().listen(3001);

Every request to add on the public server will now relay the request to the private server. See the client example in examples/relay/client.js.

Method routing

Passing a property named router in the server options will enable you to write your own logic for routing requests to specific functions.

Server example with custom routing logic in examples/method_routing/server.js:

var jayson = require('jayson');

var methods = {
  add: function(args, callback) {
    callback(null, args[0] + args[1]);
  }
};

var server = jayson.server(methods, {
  router: function(method, params) {
    // regular by-name routing first
    if(typeof(this._methods[method]) === 'function') return this._methods[method];
    if(method === 'add_2') {
      var fn = server.getMethod('add').getHandler();
      return new jayson.Method(function(args, done) {
        args.unshift(2);
        fn(args, done);
      });
    }
  }
});

server.http().listen(3000);

Client example in examples/method_routing/client.js invoking add_2 on the above server:

var jayson = require('jayson');

// create a client
var client = jayson.client.http({
  port: 3000
});

// invoke "add_2"
client.request('add_2', [3], function(err, response) {
  if(err) throw err;
  console.log(response.result); // 5!
});

Server example of nested routes where each property is separated by a dot (you do not need to use the router option for this):

var _ = require('lodash');
var jayson = require('jayson');

var methods = {
  foo: {
    bar: function(callback) {
      callback(null, 'ping pong');
    }
  },
  math: {
    add: function(args, callback) {
      callback(null, args[0] + args[1]);
    }
  }
};

// this reduction produces an object like this: {'foo.bar': [Function], 'math.add': [Function]}
var map = _.reduce(methods, collapse('', '.'), {});
var server = jayson.server(map);

function collapse(stem, sep) {
  return function(map, value, key) {
    var prop = stem ? stem + sep + key : key;
    if(_.isFunction(value)) map[prop] = value;
    else if(_.isObject(value)) map = _.reduce(value, collapse(prop, sep), map);
    return map;
  }
}
Notes
  • If router does not return anything, the server will respond with a Method Not Found error.
  • The Server.prototype methods method, methods, removeMethod and hasMethod will not use the router method, but will operate on the internal Server.prototype._methods map.
  • The router method is expected to return instances of jayson.Method (>=1.2.0)
Method definition

You can also define server methods inside a wrapping object named jayson.Method. This allows additional options about the method to be specified. Using this wrapper - explicitly or implicitly (via server options) - makes it trivial to have your method accept a variable amount of arguments.

The method class is available as the Method or method property of require('jayson'). It supports these options:

OptionDefaultTypeDescription
handlerFunctionThe actual function that will handle a JSON-RPC request to this method
collect>= 2.0.0 true before falseBooleanCollect JSON-RPC parameters in a single function argument
paramsnull`ArrayObject

Server example showcasing most features and options in examples/method_definitions/server.js:

var jayson = require('jayson');
var _ = require('lodash');

var methods = {

  // this function will be wrapped in jayson.Method with options given to the server
  sum: function(args, done) {
    done(null, sum(args));
  },

  // this method gets the raw params as first arg to handler
  sumCollect: new jayson.Method({
    handler: function(args, done) {
      var total = sum(args);
      done(null, total);
    },
    collect: true // means "collect all JSON-RPC parameters in one arg"
  }),

  // specifies some default values (alternate definition too)
  sumDefault: jayson.Method(function(args, done) {
    var total = sum(args);
    done(null, total);
  }, {
    collect: true,
    params: {a: 2, b: 5} // map of defaults
  }),

  // this method returns true when it gets an array (which it always does)
  isArray: new jayson.Method({
    handler: function(args, done) {
      var result = _.isArray(args);
      done(null, result);
    },
    collect: true,
    params: Array // could also be "Object"
  })

};

var server = jayson.server(methods, {
  // Given as options to jayson.Method when adding the method "sum"
  collect: true,
  params: Array
});

server.http().listen(3000);

// sums all numbers in an array
function sum(list) {
  return _.reduce(list, function(sum, val) {
    return sum + val;
  }, 0);
}

Client example in examples/method_definitions/client.js:

var jayson = require('jayson');

var client = jayson.client.http({
  port: 3000
});

// invoke "sumCollect" with array
client.request('sumCollect', [3, 5, 9, 11], function(err, response) {
  if(err) throw err;
  console.log(response.result); // 28
});

// invoke "sumCollect" with object
client.request('sumCollect', {a: 2, b: 3, c: 4}, function(err, response) {
  if(err) throw err;
  console.log(response.result); // 9
});

// invoke "sumDefault" with object missing some defined members
client.request('sumDefault', {b: 10}, function(err, response) {
  if(err) throw err;
  console.log(response.result); // 12
});

// invoke "isArray" with an Object
client.request('isArray', {a: 5, b: 2, c: 9}, function(err, response) {
  if(err) throw err;
  console.log(response.result); // true
});

client.request('sum', [1, 2, 3], function(err, response) {
  if(err) throw err;
  console.log(response.result); // 6
});
Notes
  • Adding methods as a plain JavaScript function creates an instance of jayson.Method internally. For backwards compatibility it will be created with the option "collect" set to false (v2.0.0). It is possible to affect this by passing the collect option to the server. This works similarly for the params option.
Server events

In addition to events that are specific to certain interfaces, all servers will emit the following events:

EventWhenArgumentsNotes
requestInterpretable non-batch request received1: Request object
responseReturning a response1: Request object 2: Response object
batchInterpretable batch request received1. Array of requestsEmits request for every part
Server Errors

If you should like to return an error from an method request to indicate a failure, remember that the JSON-RPC 2.0 specification requires the error to be an Object with a code (Integer/Number) to be regarded as valid. You can also provide a message (String) and a data (Object) with additional information. Example:

var jayson = require('jayson');

var server = jayson.server({
  i_cant_find_anything: function(args, callback) {
    var error = {code: 404, message: 'Cannot find ' + args.id};
    callback(error); // will return the error object as given
  },
  i_cant_return_a_valid_error: function(callback) {
    callback({message: 'I forgot to enter a code'}); // will return a pre-defined "Internal Error"
  }
});
Predefined Errors

It is also possible to cause a method to return one of the predefined JSON-RPC 2.0 error codes using the server helper function Server.prototype.error inside of a server method. Example:

var jayson = require('jayson');

var server = jayson.server({
  invalid_params: function(args, callback) {
    var error = this.error(-32602); // returns an error with the default properties set
    callback(error);
  }
});

You can even override the default messages:

var jayson = require('jayson');

var server = jayson.server({
  error_giver_of_doom: function(callback) {
    callback(true) // invalid error format, which causes an Internal Error to be returned instead
  }
});

// Override the default message
server.errorMessages[Server.errors.INTERNAL_ERROR] = 'I has a sad. I cant do anything right';
Server CORS

Jayson does not include functionality for supporting CORS requests natively but it is easy to use a CORS-enabling middleware like cors. An example of this can be found in examples/cors/server.js:

var jayson = require('jayson');
var cors = require('cors');
var connect = require('connect');
var jsonParser = require('body-parser').json;
var app = connect();

var server = jayson.server({
  myNameIs: function(args, callback) {
    callback(null, 'Your name is: ' + args.name);
  }
});

app.use(cors({methods: ['POST']}));
app.use(jsonParser());
app.use(server.middleware());

app.listen(3000);

Revivers and Replacers

JSON lacks support for representing types other than the simple ones defined in the JSON specification. Fortunately the JSON methods in JavaScript (JSON.parse and JSON.stringify) provide options for custom serialization/deserialization routines. Jayson allows you to pass your own routines as options to both clients and servers.

Simple example transferring the state of an object between a client and a server:

Shared code between the server and the client in examples/reviving_and_replacing/shared.js:

var Counter = exports.Counter = function(value) {
  this.count = value || 0;
};

Counter.prototype.increment = function() {
  this.count += 1;
};

exports.replacer = function(key, value) {
  if(value instanceof Counter) {
    return {$class: 'counter', $props: {count: value.count}};
  }
  return value;
};

exports.reviver = function(key, value) {
  if(value && value.$class === 'counter') {
    var obj = new Counter();
    for(var prop in value.$props) obj[prop] = value.$props[prop];
    return obj;
  }
  return value;
};

Server example in examples/reviving_and_replacing/server.js:

var jayson = require('jayson');
var shared = require('./shared');

// Set the reviver/replacer options
var options = {
  reviver: shared.reviver,
  replacer: shared.replacer
};

// create a server
var server = jayson.server({
  increment: function(args, callback) {
    args.counter.increment();
    callback(null, args.counter);
  }
}, options);

server.http().listen(3000);

A client example in examples/reviving_and_replacing/client.js invoking "increment" on the server:

var jayson = require('jayson');
var shared = require('./shared');

var client = jayson.client.http({
  port: 3000,
  reviver: shared.reviver,
  replacer: shared.replacer
});

// create the object
var params = {
  counter: new shared.Counter(2)
}

// invoke "increment"
client.request('increment', params, function(err, response) {
  if(err) throw err;
  var result = response.result;
  console.log(
    result instanceof shared.Counter, // true
    result.count, // 3
    params.counter === result // false - result is a new object
  );
});
Notes
  • Instead of using a replacer, it is possible to define a toJSON method for any JavaScript object. Unfortunately there is no corresponding method for reviving objects (that would not work, obviously), so the reviver always has to be set up manually.

Named parameters

It is possible to specify named parameters when doing a client request by passing an Object instead of an Array.

Client example in examples/named_parameters/client.js:

var jayson = require('jayson');

var client = jayson.client.http({
  port: 3000
});

client.request('add', {b: 1, a: 2}, function(err, response) {
  if(err) throw err;
  console.log(response.result); // 3!
});

Server example in examples/named_parameters/server.js:

var jayson = require('jayson');

var server = jayson.server({
  add: function(a, b, callback) {
    callback(null, a + b);
  }
}, {
  collect: false // don't collect params in a single argument
});

server.http().listen(3000);
Notes
  • If requesting methods on a Jayson server, arguments left out will be undefined
  • Too many arguments or arguments with invalid names will be ignored
  • It is assumed that the last argument to a server method is the callback and it will not be filled with something else
  • Parsing a function signature and filling in arguments is generally not recommended and should be avoided

Promises

Since version 2.0.0

A separate tree that does limited usage of the ES6 Promise object is available. The internal API remains callback based, with the addition that promises may be used for two things:

  • Returning a Promise when requesting a JSON-RPC method using a Client
  • Returning a Promise inside of a Server method

To use the separate tree, do a require('jayson/promise') instead of require('jayson').

Server example in examples/promise/server.js showing how to return a Promise in a server method:

var jayson = require('../../promise');
var _ = require('lodash');

var server = jayson.server({

  add: function(args) {
    return new Promise(function(resolve, reject) {
      var sum = _.reduce(args, function(sum, value) { return sum + value; }, 0);
      resolve(sum);
    });
  },

  // example on how to reject
  rejection: function(args) {
    return new Promise(function(resolve, reject) {
      // server.error just returns {code: 501, message: 'not implemented'}
      reject(server.error(501, 'not implemented'));
    });
  }

});

server.http().listen(3000);

Client example in examples/promise/client.js showing how to do a request:

var jayson = require('../../promise');

var client = jayson.client.http({
  port: 3000
});

var reqs = [
  client.request('add', [1, 2, 3, 4, 5]),
  client.request('rejection', [])
];

Promise.all(reqs).then(function(responses) {
  console.log(responses[0].result);
  console.log(responses[1].error);
});
Notes
  • JSON-RPC errors will not result in rejection of the Promise. It is however possible that a future version will include a client setting to have JSON-RPC errors result in rejection. Please note that network errors and the like will result in rejection.
  • A Promise is considered to have been returned from a server method if the returned object has a property then that is a function.
Promise Batches

Since version 2.0.5

Sometimes you may want to return raw requests from a promise client. This needs to be handled differently, because PromiseClient.prototype.request would normally always be expected to return a Promise which we in this case don't want.

To solve this, we need to set the fourth parameter to PromiseClient.prototype.request explicitly to false in order to not return a Promise.

Client example in examples/promise_batches/client.js showing how to properly execute a batch request:

var jayson = require('../../promise');

var client = jayson.client.http({
  port: 3000
});

var batch = [
  client.request('add', [1, 2, 3, 4, 5], undefined, false),
  client.request('add', [5, 6, 7, 8, 9], undefined, false),
];

client.request(batch).then(function(responses) {
  console.log(responses[0].result); // 15
  console.log(responses[1].result); // 35
});
Notes
  • The third parameter to PromiseClient.prototype.request above is explicitly set to undefined - this parameter would normally represent the desired ID of the call. Remember that null would mean a notification (which does not return a response) and other falsy values may actually be used as ids. Setting undefined ensures that the id is generated automatically.

Contributing

Highlighting issues or submitting pull requests on Github is most welcome.

Please make sure to follow the style of the project, and lint your code with npm run lint before submitting a patch.

Keywords

FAQs

Last updated on 04 Dec 2017

Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc