domotz-remote-pawn
Version:
Domotz Agent
1,003 lines (735 loc) • 38 kB
Markdown
# Jayson
Jayson is a [JSON-RPC 2.0][jsonrpc-spec] and [1.0][jsonrpc1-spec] compliant server and client written in JavaScript for [node.js][node.js] that aims to be as simple as possible to use.
[jsonrpc-spec]: http://jsonrpc.org/spec.html
[jsonrpc1-spec]: http://json-rpc.org/wiki/specification
[node.js]: http://nodejs.org/
[jayson-npm]: https://www.npmjs.com/package/jayson
[jayson-travis]: https://travis-ci.org/tedeh/jayson
[badge-travis]: https://img.shields.io/travis/tedeh/jayson/master.svg?style=flat-square
[badge-npm]: https://img.shields.io/npm/v/jayson.svg?style=flat-square
[badge-downloads-month]: https://img.shields.io/npm/dm/jayson.svg?style=flat-square
[![travis build status][badge-travis]][jayson-travis]
[![npm version][badge-npm]][jayson-npm]
[![npm][badge-downloads-month]][jayson-npm]
## Table of contents
- [Features](#features)
- [Example](#example)
- [Installation](#installation)
- [Changelog](#changelog-only-notable-milestones)
- [Requirements](#requirements)
- [Class Documentation](#class-documentation)
- [Running tests](#running-tests)
- [Usage](#usage)
- [Client](#client)
- [Interface description](#client-interface-description)
- [Notifications](#notifications)
- [Batches](#batches)
- [Callback syntactic sugar](#client-callback-syntactic-sugar)
- [Events](#client-events)
- [Server](#server)
- [Interface description](#server-interface-description)
- [Many interfaces at the same time](#many-interfaces-at-the-same-time)
- [Using the server as a relay](#using-the-server-as-a-relay)
- [Method routing](#method-routing)
- [Method definition](#method-definition)
- [Events](#server-events)
- [Errors](#server-errors)
- [CORS](#server-cors)
- [Revivers and replacers](#revivers-and-replacers)
- [Named parameters](#named-parameters)
- [Promises](#promises)
- [Batches](#promise-batches)
- [Contributing](#contributing)
## Features
* [Servers that can listen to several interfaces at the same time](#many-interfaces-at-the-same-time)
* Supports both HTTP and TCP client and server connections
* [Server-side method routing](#method-routing)
* [Relaying of requests to other servers](#using-the-server-as-a-relay)
* [JSON reviving and replacing for transparent serialization of complex objects](#revivers-and-replacers)
* [CLI client](#cli-client)
* [Promises](#promises)
* Fully tested to comply with the [official JSON-RPC 2.0 specification][jsonrpc-spec]
* Also supports [JSON-RPC 1.0][jsonrpc1-spec]
## Example
A basic JSON-RPC 2.0 server via HTTP:
Server example in [examples/simple_example/server.js](examples/simple_example/server.js):
```javascript
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](examples/simple_example/client.js) invoking `add` on the above server:
```javascript
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](https://github.com/isaacs/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](#promises)
- _Breaking_: `collect: true` is now the default option for a new `jayson.Server` and `jayson.Method`
- *1.2.0*
- Greatly improved [server method definition](#method-definition)
- *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][jayson-travis] using [travis-ci](https://travis-ci.org/) 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][jsdoc-spec] is available at [jayson.tedeh.net](http://jayson.tedeh.net).
[jsdoc-spec]: http://usejsdoc.org/
## 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
| Name | Description |
| --------------- | --------------- |
| `Client` | Base class |
| `Client.tcp` | TCP interface |
| `Client.tls` | TLS interface |
| `Client.http` | HTTP interface |
| `Client.https` | HTTPS interface |
Every client supports these options:
| Option | Default | Type | Description |
|------------- |------------------------------------ |------------ |---------------------------------------------------------- |
| `reviver` | `undefined` | `Function` | `JSON.parse` reviver |
| `replacer` | `undefined` | `Function` | `JSON.stringify` replacer |
| `generator` | [RFC4122][rfc_4122_spec] generator | `Function` | Generates a `String` for request ID. |
| `version` | 2 | `Number` | JSON-RPC version to support (1 or 2) |
[rfc_4122_spec]: http://www.ietf.org/rfc/rfc4122.txt
##### Client.http
Uses the same options as [http.request][nodejs_docs_http_request] in addition to these options:
| Option | Default | Type | Description |
|------------ |--------- |---------- |-------------------------------- |
| `encoding` | `utf8` | `String` | Determines the encoding to use |
###### Client.http Events
The HTTP server will emit the following events:
| Event | When | Arguments | Notes |
|----------------- |----------------------------------- |--------------------------------------------------------------------------- |------------------------------------------- |
| `http request` | Created an HTTP request | 1. Instance of `http.ClientRequest` | |
| `http response` | Received an HTTP response | 1. Instance of `http.IncomingMessage` 2. Instance of `http.ClientRequest` | |
| `http error` | Underlying stream emits `error` | 1. Error | |
| `http timeout` | Underlying stream emits `timeout` | | Automatically 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][nodejs_docs_url_parse]. Example:
```javascript
var jayson = require('jayson');
var client = jayson.client.http('http://localhost:3000');
// client.options is now the result of url.parse
```
[nodejs_docs_http_request]: http://nodejs.org/docs/latest/api/http.html#http_http_request_options_callback
[nodejs_docs_url_parse]: http://nodejs.org/api/url.html#url_url_parse_urlstr_parsequerystring_slashesdenotehost
##### Client.https
Uses the same options as [https.request][nodejs_docs_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][nodejs_docs_url_parse].
Will emit the [same custom events](#clienthttp-events) as `Client.http`.
[nodejs_docs_https_request]: http://nodejs.org/api/all.html#all_https_request_options_callback
##### Client.tcp
Uses the same options as [net.connect][nodejs_docs_net_connect] in addition _to the same options as `Client.http`_.
[nodejs_docs_net_connect]: https://nodejs.org/api/net.html#net_net_connect
##### Client.tls
Uses the same options as [tls.connect][nodejs_docs_tls_connect] in addition _to the same options as `Client.http`_.
[nodejs_docs_tls_connect]: https://nodejs.org/api/tls.html#tls_tls_connect_options_callback
#### 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](examples/notifications/client.js) doing a notification request:
```javascript
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](examples/notifications/server.js):
```javascript
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][jsonrpc-spec] 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](examples/batch_request/index.js):
```javascript
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][jsonrpc-spec] 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):
| Event | When | Arguments | Notes |
|------------ |----------------------------- |----------------------------------------------- |------- |
| `request` | About to dispatch a request | 1: Request object | |
| `response` | Received a response | 1: 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
| Name | Description |
|--------------------- |-------------------------------------------------------------------------------------------- |
| `Server` | Base interface for a server that supports receiving JSON-RPC requests |
| `Server.tcp` | TCP server that inherits from [net.Server][nodejs_doc_net_server] |
| `Server.tls` | TLS server that inherits from [tls.Server][nodejs_doc_tls_server] |
| `Server.http` | HTTP server that inherits from [http.Server][nodejs_doc_http_server] |
| `Server.https` | HTTPS server that inherits from [https.Server][nodejs_doc_https_server] |
| `Server.middleware` | Method that returns a [Connect][connect]/[Express][express] compatible middleware function |
[nodejs_doc_net_server]: http://nodejs.org/docs/latest/api/net.html#net_class_net_server
[nodejs_doc_http_server]: http://nodejs.org/docs/latest/api/http.html#http_class_http_server
[nodejs_doc_https_server]: http://nodejs.org/docs/latest/api/https.html#https_class_https_server
[nodejs_doc_tls_server]: https://nodejs.org/api/tls.html#tls_class_tls_server
[connect]: http://www.senchalabs.org/connect/
[express]: http://expressjs.com/
Servers supports these options:
| Option | Default | Type | Description |
|--------------------- |----------------- |--------------------- |----------------------------------------------------------- |
| `reviver` | `null` | `Function` | `JSON.parse` reviver |
| `replacer` | `null ` | `Function` | `JSON.stringify` replacer |
| `router` | `null ` | `Function` | Return the function for [method routing](#method-routing) |
| `collect` | `true` | `Boolean` | Passed to `methodConstructor` options |
| `params` | `undefined` | `Array|Object|null` | Passed to `methodConstructor` options |
| `methodConstructor` | `jayson.Method` | `Function` | Server functions are made an instance of this class |
| `version` | 2 | `Number` | JSON-RPC version to support (1 or 2) |
##### Server.tcp
Uses the same options as the base class. Inherits from [net.Server][nodejs_doc_net_server].
##### Server.tls
Uses the same options as the base class. Inherits from [tls.Server][nodejs_doc_tls_server].
##### Server.http
Uses the same options as the base class. Inherits from [http.Server][nodejs_doc_http_server].
###### Server.http Events
| Event | When | Arguments | Notes |
|----------------- |------------------------------- |------------------------------------------------------------------------------ |------- |
| `http request` | Incoming HTTP request | 1. Instance of `http.IncomingMessage` | |
| `http response` | About to send a HTTP response | 1. Instance of `http.ServerResponse` 2. Instance of `http. IncomingMessage ` | |
##### Server.https
Uses the same options as the base class. Inherits from [https.Server][nodejs_doc_https_server] and `jayson.Server.http`. For information on how to configure certificates, [see the documentation on https.Server][nodejs_doc_https_server].
Will emit the [same custom events](#serverhttp-events) as `Server.http`.
##### Server.middleware
Uses the same options as the base class. Returns a function that is compatible with [Connect][connect] or [Express][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:
| Option | Default | Type | Description |
|-------- |--------- |----------- |------------------------------------------------------------------------------------------- |
| `end` | `true` | `Boolean` | If set to `false` causes the middleware to `next()` instead of `res.end()` when finished. |
Middleware example in [examples/middleware/server.js](examples/middleware/server.js):
```javascript
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](examples/many_interfaces/server.js) that listens to both `http` and a `https` requests:
```javascript
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](examples/relay/server_public.js) listening on `*:3000`:
```javascript
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](examples/relay/server_private.js) listening on `*:3001`:
```javascript
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](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](examples/method_routing/server.js):
```javascript
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](examples/method_routing/client.js) invoking `add_2` on the above server:
```javascript
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):
```javascript
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:
| Option | Default | Type | Description |
|----------- |-------------------------------- |--------------------- |------------------------------------------------------------------------ |
| `handler` | | `Function` | The actual function that will handle a JSON-RPC request to this method |
| `collect` | >= 2.0.0 `true` before `false` | `Boolean` | Collect JSON-RPC parameters in a single function argument |
| `params` | null | `Array|Object|null` | Force JSON-RPC parameters to be of a certain type |
Server example showcasing most features and options in [examples/method_definitions/server.js](examples/method_definitions/server.js):
```javascript
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](examples/method_definitions/client.js):
```javascript
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:
| Event | When | Arguments | Notes |
|------------ |------------------------------------------ |-------------------------------------- |-------------------------------- |
| `request` | Interpretable non-batch request received | 1: Request object | |
| `response` | Returning a response | 1: Request object 2: Response object | |
| `batch` | Interpretable batch request received | 1. Array of requests | Emits `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][jsonrpc-spec] 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:
```javascript
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][jsonrpc-spec#error_object] using the server helper function `Server.prototype.error` inside of a server method. Example:
[jsonrpc-spec#error_object]: http://jsonrpc.org/spec.html#error_object
```javascript
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:
```javascript
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](https://github.com/expressjs/cors). An example of this can be found in [examples/cors/server.js](examples/cors/server.js):
```javascript
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][jsonrpc-spec]. 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](examples/reviving_and_replacing/shared.js):
```javascript
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](examples/reviving_and_replacing/server.js):
```javascript
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](examples/reviving_and_replacing/client.js) invoking "increment" on the server:
```javascript
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](examples/named_parameters/client.js):
```javascript
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](examples/named_parameters/server.js):
```javascript
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
[es6-promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
*Since version 2.0.0*
A separate tree that does limited usage of the [ES6 Promise][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](examples/promise/server.js) showing how to return a `Promise` in a server method:
```javascript
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](examples/promise/client.js) showing how to do a request:
```javascript
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](examples/promise/client.js) showing how to properly execute a batch request:
```javascript
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](https://github.com/tedeh/jayson/issues) or submitting pull
requests on [Github](https://github.com/tedeh/jayson) 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.