Skip to content

Latest commit

 

History

History
494 lines (352 loc) · 16.2 KB

README.md

File metadata and controls

494 lines (352 loc) · 16.2 KB

Jayson

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

Features

  • Servers that listen to many interfaces at once
  • Supports HTTP client and server connections
  • jQuery AJAX client
  • Automatic request relaying to other servers
  • Simple process forking for expensive computations
  • JSON Reviving and Replacing for advanced (de)serialization of objects
  • Binary client
  • Extensively tested to comply with the official specification

Example

A basic JSON-RPC 2.0 server via HTTP:

Server in examples/simple_example/server.js:

var jayson = require(__dirname + '/../..');

// create a server
var server = jayson.server({
  add: function(a, b, callback) {
    callback(null, a + b);
  }
});

// Bind a http interface to the server and let it listen to localhost:3000
server.http().listen(3000);

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

var jayson = require(__dirname + '/../..');

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

// invoke "add"
client.request('add', [1, 1], function(err, error, response) {
  if(err) throw err;
  console.log(response); // 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 binary in your PATH.

Binary client

There is a binary client in bin/jayson.js and it should be available as jayson if you installed the package with the --global switch. 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 has been tested with the following node.js versions:

  • node.js v0.6.x
  • node.js v0.8.x

Class documentation

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

Running tests

  • Change directory to the repository root
  • Install the testing framework (mocha together with should) by executing npm install --dev
  • Run the tests with make test or npm test

Usage

Client

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

Interfaces

  • Client Base class for interfacing with a server.
  • Client.http HTTP interface.
  • Client.fork Node.js child_process/fork interface.
  • Client.jquery Wrapper around jQuery.ajax.

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 in examples/notifications/client.js doing a notification request:

var jayson = require(__dirname + '/../..');

var client = jayson.client.http({
  host: 'localhost',
  port: 3000
});

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

A server in examples/notifications/server.js:

var jayson = require(__dirname + '/../..');

var server = jayson.server({
  ping: function(callback) {
    // do something
    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.

Client example in examples/batch_request/client.js:

var jayson = require(__dirname + '/../..');
var client = jayson.client.http({
  host: 'localhost',
  port: 3000
});

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

// callback takes two arguments (first type of callback)
client.request(batch, function(err, responses) {
  if(err) throw err;
  // responses is an array of errors and successes together
  console.log('responses', responses);
});

// callback takes three arguments (second type of callback)
client.request(batch, function(err, errors, successes) {
  if(err) throw err;
  // errors is an array of the requests that errored
  console.log('errors', errors);
  // successes is an array of requests that succeded
  console.log('successes', successes);
});

Server example in examples/batch_request/server.js:

var jayson = require(__dirname + '/../..');

var server = jayson.server({
  add: function(a, b, callback) {
    callback(null, a + b);
  }
});

server.http().listen(3000);
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.

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 interfaces and options

Every client supports these options:

  • reviver -> Function to use as a JSON reviver
  • replacer -> Function to use as a JSON replacer
  • generator -> Function to generate request ids with. If omitted, Jayson will just generate a "random" number like this: Math.round(Math.random() * Math.pow(2, 24))

Client events

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

  • request Emitted when a client is just about to dispatch a request. First argument is the request object.
  • response Emitted when a client has just received a reponse. First argument is the request object, second argument is the response as received.
Client.http

Uses the same options as http.request (which also enables the use of https) in addition to these options:

  • encoding -> String that determines the encoding to use and defaults to utf8
Client.fork

Uses the same options as the base class.

Client.jquery

The jQuery Client is stand-alone from the other classes and should preferably be compiled with make compile which outputs different flavors into the build directory. Supports inclusion via AMD. Uses the same options as jQuery.ajax and exposes itself as $.jayson with the same arguments as Client.prototype.request.

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 interfaces and options

  • Server - Base interface for a server that supports receiving JSON-RPC 2.0 requests.
  • Server.http - HTTP server that inherits from http.Server.
  • Server.https - HTTPS server that inherits from https.Server.
  • Server.middleware - Method that returns a Connect/Express compatible middleware function.
  • Server.fork Creates a child process that can take requests via client.fork

Using many server interfaces at the same time

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

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

var jayson = require(__dirname + '/../..');

var server = jayson.server({
  add: function(a, b, callback) {
    return callback(null, a + b);
  }
});

// "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 fork/server or to abstract a cluster of servers behind a common interface.

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

var jayson = require(__dirname + '/../..');

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

// let the server listen to *:3000
server.http().listen(3000, function() {
  console.log('Listening on *:3000');
});

Private server in examples/relay/server_private.js listening on localhost:3001:

var jayson = require(__dirname + '/../..');

var server = jayson.server({
  add: function(a, b, callback) {
    callback(null, a + b);
  }
});

// let the private server listen to localhost:3001
server.http().listen(3001, '127.0.0.1', function() {
  console.log('Listening on 127.0.0.1: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.

Server events

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

  • request Emitted when the server receives an interpretable request. First argument is the request object.
  • response Emitted when the server is returning a response. First argument is the request object, the second is the response object.

Revivers and Replacers

JSON is a great data format, but it 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) provides 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 in examples/reviving_and_replacing/server.js:

var jayson = require(__dirname + '/../..');
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(counter, callback) {
    counter.increment();
    callback(null, counter);
  }
}, options);

// let the server listen to for http connections on localhost:3000
server.http().listen(3000);

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

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

// create a client with the shared reviver and replacer
var client = jayson.client.http({
  port: 3000,
  hostname: 'localhost',
  reviver: shared.reviver,
  replacer: shared.replacer
});

// create the object
var instance = new shared.Counter(2);

// invoke "increment"
client.request('increment', [instance], function(err, error, result) {
  if(err) throw err;
  console.log(result instanceof shared.Counter); // true
  console.log(result.count); // 3!
  console.log(instance === result); // false - it won't be the same object, naturally
});
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.

Forking

It is possible (and simple) to create automatic forks with jayson using the node.js child_process core library. This might be used for expensive or blocking calculations and to provide some separation from the main server thread.

The forking server class is available as jayson.Server.Fork and takes a file as the first option. This file will be require'd by jayson and should export any methods that are to be made available to clients.

The main server in examples/forking/server.js

var jayson = require(__dirname + '/../..');

// creates a fork
var fork = jayson.server.fork(__dirname + '/fork');

var front = jayson.server({
  fib: jayson.client.fork(fork) // connects "fib" to the fork
});

// let the front server listen to localhost:3000
front.http().listen(3000);

The forked server in examples/forking/fork.js

// export "fib" for forking
exports.fib = function(n, callback) {
  function fib(n) {
    if(n < 2) return n;
    return fib(n - 1) + fib(n - 2);
  };
  var result = fib(n);
  callback(null, fib(n));
};

A client doing a fibonacci request in examples/forking/client.js

var jayson = require(__dirname + '/../..');

var client = jayson.client.http({
  port: 3000,
  hostname: 'localhost'
});

// request "fib" on the server
client.request('fib', [15], function(err, response) {
  console.log(response);
});

Notes

  • A child_process is spawned immediately
  • To specify options (such as a reviver and a replacer) for the forked server, module.exports an instance of jayson.Server instead of exporting plain methods.

Contributing

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

TODO

  • jayson.Server.fork.deferred - Deferred forking that only spawns on demand
  • Streaming
  • Middleware-like support for defining server methods
  • Integration with the Cluster API
  • Benchmarks