UNPKG

49 kBMarkdownView Raw
1# Jayson
2
3Jayson 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.
4
5[jsonrpc-spec]: http://jsonrpc.org/spec.html
6[jsonrpc1-spec]: http://json-rpc.org/wiki/specification
7[node.js]: http://nodejs.org/
8[jayson-npm]: https://www.npmjs.com/package/jayson
9[jayson-travis]: https://travis-ci.org/tedeh/jayson
10[badge-travis]: https://img.shields.io/travis/tedeh/jayson/master.svg?style=flat-square
11[badge-npm]: https://img.shields.io/npm/v/jayson.svg?style=flat-square
12[badge-downloads-month]: https://img.shields.io/npm/dm/jayson.svg?style=flat-square
13
14[![travis build status][badge-travis]][jayson-travis]
15[![npm version][badge-npm]][jayson-npm]
16[![npm][badge-downloads-month]][jayson-npm]
17
18## Table of contents
19
20- [Features](#features)
21- [Example](#example)
22- [Installation](#installation)
23- [Changelog](#changelog-only-notable-milestones)
24- [Requirements](#requirements)
25- [Class Documentation](#class-documentation)
26- [Running tests](#running-tests)
27- [Typescript](#typescript)
28- [Usage](#usage)
29 - [Client](#client)
30 - [Interface description](#client-interface-description)
31 - [Browser usage](#clientbrowser)
32 - [Notifications](#notifications)
33 - [Batches](#batches)
34 - [Callback syntactic sugar](#client-callback-syntactic-sugar)
35 - [Events](#client-events)
36 - [Server](#server)
37 - [Interface description](#server-interface-description)
38 - [Many interfaces at the same time](#many-interfaces-at-the-same-time)
39 - [Using the server as a relay](#using-the-server-as-a-relay)
40 - [Method routing](#method-routing)
41 - [Method definition](#method-definition)
42 - [Events](#server-events)
43 - [Errors](#server-errors)
44 - [CORS](#server-cors)
45 - [Context](#server-context)
46- [Revivers and replacers](#revivers-and-replacers)
47- [Named parameters](#named-parameters)
48- [Promises](#promises)
49 - [Batches](#promise-batches)
50- [FAQ](#faq)
51- [Recommended usage](#what-is-the-recommended-way-to-use-jayson)
52- [Contributing](#contributing)
53
54## Features
55
56* [Servers that can listen to several interfaces at the same time](#many-interfaces-at-the-same-time)
57* Supports both HTTP and TCP client and server connections
58* [Server-side method routing](#method-routing)
59* [Relaying of requests to other servers](#using-the-server-as-a-relay)
60* [JSON reviving and replacing for transparent serialization of complex objects](#revivers-and-replacers)
61* [CLI client](#cli-client)
62* [Promises](#promises)
63* Fully tested to comply with the [official JSON-RPC 2.0 specification][jsonrpc-spec]
64* Also supports [JSON-RPC 1.0][jsonrpc1-spec]
65
66## Example
67
68A basic JSON-RPC 2.0 server via HTTP:
69
70Server example in [examples/simple_example/server.js](examples/simple_example/server.js):
71
72```javascript
73'use strict';
74
75const jayson = require('./../..');
76
77// create a server
78const server = jayson.server({
79 add: function(args, callback) {
80 callback(null, args[0] + args[1]);
81 }
82});
83
84server.http().listen(3000);
85```
86
87Client example in [examples/simple_example/client.js](examples/simple_example/client.js) invoking `add` on the above server:
88
89```javascript
90'use strict';
91
92const jayson = require('./../..');
93
94// create a client
95const client = jayson.client.http({
96 port: 3000
97});
98
99// invoke "add"
100client.request('add', [1, 1], function(err, response) {
101 if(err) throw err;
102 console.log(response.result); // 2
103});
104```
105
106## Installation
107
108Install the latest version of _jayson_ from [npm](https://www.npmjs.com) 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.
109
110## Changelog (only notable milestones)
111
112- *3.0.0*
113 - Can pass a [context object](#server-context) to handlers
114 - _Breaking_: `collect` option removed from `jayson.Server/Method`. JSON-RPC params to handlers are now **always** in the first argument.
115- *2.1.0*
116 - Experimental typescript support
117- *2.0.6*
118 - Clarified how to use [in the browser](#clientbrowser)
119- *2.0.0*
120 - Added [support for promises](#promises)
121 - _Breaking_: `collect: true` is now the default option for a new `jayson.Server` and `jayson.Method`
122- *1.2.0*
123 - Greatly improved [server method definition](#method-definition)
124- *1.1.1*
125 - More http server events
126 - Remove fork server and client
127 - Add server routing
128- *1.0.11*
129 Add support for a HTTPS client
130- *1.0.9*
131 Add support for TCP servers and clients
132
133### CLI client
134
135There is a basic 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.
136
137## Requirements
138
139Jayson 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/). You can look inside [.travis.yml](.travis.yml) if you want to see which versions are tested against.
140
141## Class documentation
142
143In addition to this document, a comprehensive class documentation made with [jsdoc][jsdoc-spec] is available at [jayson.tedeh.net](http://jayson.tedeh.net).
144
145[jsdoc-spec]: http://usejsdoc.org/
146
147## Running tests
148
149- Change directory to the repository root
150- Install the development packages by executing `npm install --dev`
151- Run the tests with `npm run test`
152
153## Typescript
154
155Since `v2.1.0` there is experimental typescript support available with jayson. Anyone that would like to improve on the type definitions is most welcome to provide a pull request.
156
157If you would just like to report an issue with the type definitions that you can't solve by yourself, please read the previous discussion here first: https://github.com/tedeh/jayson/issues/99
158
159## Usage
160
161### Client
162
163The client is available as the `Client` or `client` property of `require('jayson')`.
164
165#### Client interface description
166
167| Name | Description |
168| --------------- | ---------------- |
169| `Client` | Base class |
170| `Client.tcp` | TCP interface |
171| `Client.tls` | TLS interface |
172| `Client.http` | HTTP interface |
173| `Client.https` | HTTPS interface |
174| `Client.browser`| Browser interface |
175
176Every client supports these options:
177
178| Option | Default | Type | Description |
179|------------- |------------------------------------ |------------ |---------------------------------------------------------- |
180| `reviver` | `undefined` | `Function` | `JSON.parse` reviver |
181| `replacer` | `undefined` | `Function` | `JSON.stringify` replacer |
182| `generator` | [RFC4122][rfc_4122_spec] generator | `Function` | Generates a `String` for request ID. |
183| `version` | 2 | `Number` | JSON-RPC version to support (1 or 2) |
184
185[rfc_4122_spec]: http://www.ietf.org/rfc/rfc4122.txt
186
187##### Client.http
188
189Uses the same options as [http.request][nodejs_docs_http_request] in addition to these options:
190
191| Option | Default | Type | Description |
192|------------ |------------ |---------- |---------------------------------------- |
193| `encoding` | `utf8` | `String` | Determines the encoding to use |
194| `headers` | `undefined` | `Object` | Extend the headers sent by the client |
195
196###### Client.http Events
197
198The HTTP server will emit the following events:
199
200| Event | When | Arguments | Notes |
201|----------------- |----------------------------------- |--------------------------------------------------------------------------- |------------------------------------------- |
202| `http request` | Created an HTTP request | 1. Instance of `http.ClientRequest` | |
203| `http response` | Received an HTTP response | 1. Instance of `http.IncomingMessage` 2. Instance of `http.ClientRequest` | |
204| `http error` | Underlying stream emits `error` | 1. Error | |
205| `http timeout` | Underlying stream emits `timeout` | | Automatically causes the request to abort |
206
207It is possible to pass a string URL as the first argument. The URL will be run through [url.parse][nodejs_docs_url_parse]. Example:
208
209```javascript
210var jayson = require('jayson');
211var client = jayson.client.http('http://localhost:3000');
212// client.options is now the result of url.parse
213```
214
215[nodejs_docs_http_request]: http://nodejs.org/docs/latest/api/http.html#http_http_request_options_callback
216[nodejs_docs_url_parse]: http://nodejs.org/api/url.html#url_url_parse_urlstr_parsequerystring_slashesdenotehost
217
218##### Client.https
219
220Uses 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
221to pass a string URL as the first argument and have it interpreted by [url.parse][nodejs_docs_url_parse].
222
223Will emit the [same custom events](#clienthttp-events) as `Client.http`.
224
225[nodejs_docs_https_request]: http://nodejs.org/api/all.html#all_https_request_options_callback
226
227##### Client.tcp
228
229Uses the same options as [net.connect][nodejs_docs_net_connect] in addition _to the same options as `Client.http`_.
230
231[nodejs_docs_net_connect]: https://nodejs.org/api/net.html#net_net_connect
232
233##### Client.tls
234
235Uses the same options as [tls.connect][nodejs_docs_tls_connect] in addition _to the same options as `Client.http`_.
236
237[nodejs_docs_tls_connect]: https://nodejs.org/api/tls.html#tls_tls_connect_options_callback
238
239##### Client.browser
240
241The browser client is a simplified version of the regular client for use browser-side. It does not have any dependencies on node.js core libraries (but does depend on the `uuid` and `lodash`) and also does not know how to "send" a request to a server like the other clients.
242
243Because it does not depend on any core libraries, the browser client is **not** an instance of `JaysonClient` or `EventEmitter` and therefore does **not** emit any of the normal request events that the other clients do.
244
245To use the browser client, `require('jayson/lib/client/browser')` and pass a calling/transport function as the first argument. The transport function receives a JSON-RPC string request and is expected to callback with a string response received from the server (not JSON) or an error (not a JSON-RPC error).
246
247The reason for dealing with strings is to support the `reviver` and `replacer` options like the other clients.
248
249This client example in [examples/browser_client/client.js](examples/browser_client/client.js) below uses [node-fetch](https://github.com/bitinn/node-fetch) in the transport function, but a dropin replacement for use in an *actual* browser could instead use [whatwg-fetch](https://github.com/github/fetch/issues/184).
250
251```javascript
252'use strict';
253
254const jaysonBrowserClient = require('./../../lib/client/browser');
255const fetch = require('node-fetch');
256
257const callServer = function(request, callback) {
258 const options = {
259 method: 'POST',
260 body: request,
261 headers: {
262 'Content-Type': 'application/json',
263 }
264 };
265
266 fetch('http://localhost:3000', options)
267 .then(function(res) { return res.text(); })
268 .then(function(text) { callback(null, text); })
269 .catch(function(err) { callback(err); });
270};
271
272const client = jaysonBrowserClient(callServer, {
273 // other options go here
274});
275
276client.request('multiply', [5, 5], function(err, error, result) {
277 if(err) throw err;
278 console.log(result); // 25
279});
280```
281
282#### Notifications
283
284Notification 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`.
285
286Client example in [examples/notifications/client.js](examples/notifications/client.js) doing a notification request:
287
288```javascript
289'use strict';
290
291const jayson = require('./../..');
292
293const client = jayson.client.http({
294 port: 3000
295});
296
297// the third parameter is set to "null" to indicate a notification
298client.request('ping', [], null, function(err) {
299 if(err) throw err;
300 console.log('ok'); // request was received successfully
301});
302```
303
304Server example in [examples/notifications/server.js](examples/notifications/server.js):
305
306```javascript
307'use strict';
308
309const jayson = require('./../..');
310
311const server = jayson.server({
312 ping: function(args, callback) {
313 // do something, do nothing
314 callback();
315 }
316});
317
318server.http().listen(3000);
319```
320
321##### Notes
322
323* Any value that the server returns will be discarded when doing a notification request.
324* 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.
325* 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.
326* See the [Official JSON-RPC 2.0 Specification][jsonrpc-spec] for additional information on how Jayson handles notifications that are erroneous.
327
328#### Batches
329
330A 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`.
331
332Combined server/client example in [examples/batch_request/index.js](examples/batch_request/index.js):
333
334```javascript
335'use strict';
336
337const jayson = require('./../..');
338
339const server = jayson.server({
340 add: function(args, callback) {
341 callback(null, args[0] + args[1]);
342 }
343});
344
345const client = jayson.client(server);
346
347const batch = [
348 client.request('does_not_exist', [10, 5]),
349 client.request('add', [1, 1]),
350 client.request('add', [0, 0], null) // a notification
351];
352
353client.request(batch, function(err, errors, successes) {
354 if(err) throw err;
355 console.log('errors', errors); // array of requests that errored
356 console.log('successes', successes); // array of requests that succeeded
357});
358
359client.request(batch, function(err, responses) {
360 if(err) throw err;
361 console.log('responses', responses); // all responses together
362});
363```
364
365##### Notes
366
367* 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.
368* 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.
369
370#### Client callback syntactic sugar
371
372When the length (number of arguments) of a client callback function is either 2 or 3 it receives slightly different values when invoked.
373
374* 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.
375* 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).
376
377When 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.
378
379#### Client events
380
381A client will emit the following events (in addition to any special ones emitted by a specific interface):
382
383| Event | When | Arguments | Notes |
384|------------ |----------------------------- |----------------------------------------------- |------- |
385| `request` | About to dispatch a request | 1: Request object | |
386| `response` | Received a response | 1: Request object 2: Response object received | |
387
388### Server
389
390The server classes are available as the `Server` or `server` property of `require('jayson')`.
391
392The server also sports several interfaces that can be accessed as properties of an instance of `Server`.
393
394#### Server interface description
395
396| Name | Description |
397|--------------------- |-------------------------------------------------------------------------------------------- |
398| `Server` | Base interface for a server that supports receiving JSON-RPC requests |
399| `Server.tcp` | TCP server that inherits from [net.Server][nodejs_doc_net_server] |
400| `Server.tls` | TLS server that inherits from [tls.Server][nodejs_doc_tls_server] |
401| `Server.http` | HTTP server that inherits from [http.Server][nodejs_doc_http_server] |
402| `Server.https` | HTTPS server that inherits from [https.Server][nodejs_doc_https_server] |
403| `Server.middleware` | Method that returns a [Connect][connect]/[Express][express] compatible middleware function |
404
405[nodejs_doc_net_server]: http://nodejs.org/docs/latest/api/net.html#net_class_net_server
406[nodejs_doc_http_server]: http://nodejs.org/docs/latest/api/http.html#http_class_http_server
407[nodejs_doc_https_server]: http://nodejs.org/docs/latest/api/https.html#https_class_https_server
408[nodejs_doc_tls_server]: https://nodejs.org/api/tls.html#tls_class_tls_server
409[connect]: http://www.senchalabs.org/connect/
410[express]: http://expressjs.com/
411
412Servers supports these options:
413
414| Option | Default | Type | Description |
415|--------------------- |----------------- |--------------------- |----------------------------------------------------------- |
416| `reviver` | `null` | `Function` | `JSON.parse` reviver |
417| `replacer` | `null ` | `Function` | `JSON.stringify` replacer |
418| `router` | `null ` | `Function` | Return the function for [method routing](#method-routing) |
419| `useContext` | `false` | `Boolean` | Passed to `methodConstructor` options |
420| `params` | `undefined` | `Array/Object/null` | Passed to `methodConstructor` options |
421| `methodConstructor` | `jayson.Method` | `Function` | Server functions are made an instance of this class |
422| `version` | 2 | `Number` | JSON-RPC version to support (1 or 2) |
423
424##### Server.tcp
425
426Uses the same options as the base class. Inherits from [net.Server][nodejs_doc_net_server].
427
428##### Server.tls
429
430Uses the same options as the base class. Inherits from [tls.Server][nodejs_doc_tls_server].
431
432##### Server.http
433
434Uses the same options as the base class. Inherits from [http.Server][nodejs_doc_http_server].
435
436###### Server.http Events
437
438| Event | When | Arguments | Notes |
439|----------------- |------------------------------- |------------------------------------------------------------------------------ |------- |
440| `http request` | Incoming HTTP request | 1. Instance of `http.IncomingMessage` | |
441| `http response` | About to send a HTTP response | 1. Instance of `http.ServerResponse` 2. Instance of `http. IncomingMessage ` | |
442
443##### Server.https
444
445Uses 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].
446
447Will emit the [same custom events](#serverhttp-events) as `Server.http`.
448
449##### Server.middleware
450
451Uses 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.
452
453The middleware supports the following options:
454
455| Option | Default | Type | Description |
456|-------- |--------- |----------- |------------------------------------------------------------------------------------------- |
457| `end` | `true` | `Boolean` | If set to `false` causes the middleware to `next()` instead of `res.end()` when finished. |
458
459Middleware example in [examples/middleware/server.js](examples/middleware/server.js):
460
461```javascript
462'use strict';
463
464const jayson = require('./../..');
465const jsonParser = require('body-parser').json;
466const connect = require('connect');
467const app = connect();
468
469const server = jayson.server({
470 add: function(args, callback) {
471 callback(null, args[0] + args[1]);
472 }
473});
474
475// parse request body before the jayson middleware
476app.use(jsonParser());
477app.use(server.middleware());
478
479app.listen(3000);
480```
481
482#### Many interfaces at the same time
483
484A Jayson server can use many interfaces at the same time.
485
486Server example in [examples/many_interfaces/server.js](examples/many_interfaces/server.js) that listens to both `http` and a `https` requests:
487
488```javascript
489'use strict';
490
491const jayson = require('./../..');
492
493const server = jayson.server();
494
495// "http" will be an instance of require('http').Server
496const http = server.http();
497
498// "https" will be an instance of require('https').Server
499const https = server.https({
500 //cert: require('fs').readFileSync('cert.pem'),
501 //key require('fs').readFileSync('key.pem')
502});
503
504http.listen(80, function() {
505 console.log('Listening on *:80');
506});
507
508https.listen(443, function() {
509 console.log('Listening on *:443');
510});
511```
512
513#### Using the server as a relay
514
515Passing 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.
516
517Frontend server example in [examples/relay/server_public.js](examples/relay/server_public.js) listening on `*:3000`:
518
519```javascript
520'use strict';
521
522const jayson = require('./../..');
523
524// create a server where "add" will relay a localhost-only server
525const server = jayson.server({
526 add: jayson.client.http({
527 port: 3001
528 })
529});
530
531// let the frontend server listen to *:3000
532server.http().listen(3000);
533```
534
535Backend server example in [examples/relay/server_private.js](examples/relay/server_private.js) listening on `*:3001`:
536
537```javascript
538'use strict';
539
540const jayson = require('./../..');
541
542const server = jayson.server({
543 add: function(args, callback) {
544 callback(null, args[0] + args[1]);
545 }
546});
547
548// let the backend listen to *:3001
549server.http().listen(3001);
550```
551
552Every 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).
553
554#### Method routing
555
556Passing a property named `router` in the server options will enable you to write your own logic for routing requests to specific functions.
557
558Server example with custom routing logic in [examples/method_routing/server.js](examples/method_routing/server.js):
559
560```javascript
561'use strict';
562
563const jayson = require('./../..');
564
565const methods = {
566 add: function(args, callback) {
567 callback(null, args[0] + args[1]);
568 }
569};
570
571const server = jayson.server(methods, {
572 router: function(method, params) {
573 // regular by-name routing first
574 if(typeof(this._methods[method]) === 'function') return this._methods[method];
575 if(method === 'add_2') {
576 const fn = server.getMethod('add').getHandler();
577 return new jayson.Method(function(args, done) {
578 args.unshift(2);
579 fn(args, done);
580 });
581 }
582 }
583});
584
585server.http().listen(3000);
586```
587
588Client example in [examples/method_routing/client.js](examples/method_routing/client.js) invoking `add_2` on the above server:
589
590```javascript
591'use strict';
592
593const jayson = require('./../..');
594
595// create a client
596const client = jayson.client.http({
597 port: 3000
598});
599
600// invoke "add_2"
601client.request('add_2', [3], function(err, response) {
602 if(err) throw err;
603 console.log(response.result); // 5!
604});
605```
606
607Server example of nested routes where each property is separated by a dot (you do not need to use the router option for this):
608
609```javascript
610'use strict';
611
612const _ = require('lodash');
613const jayson = require('jayson');
614
615const methods = {
616 foo: {
617 bar: function(callback) {
618 callback(null, 'ping pong');
619 }
620 },
621 math: {
622 add: function(args, callback) {
623 callback(null, args[0] + args[1]);
624 }
625 }
626};
627
628// this reduction produces an object like this: {'foo.bar': [Function], 'math.add': [Function]}
629const map = _.reduce(methods, collapse('', '.'), {});
630const server = jayson.server(map);
631
632function collapse(stem, sep) {
633 return function(map, value, key) {
634 var prop = stem ? stem + sep + key : key;
635 if(_.isFunction(value)) map[prop] = value;
636 else if(_.isObject(value)) map = _.reduce(value, collapse(prop, sep), map);
637 return map;
638 }
639}
640```
641
642##### Notes
643
644* If `router` does not return anything, the server will respond with a `Method Not Found` error.
645* 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.
646* The `router` method is expected to return instances of `jayson.Method` (>=1.2.0)
647
648#### Method definition
649
650You 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.
651
652The method class is available as the `Method` or `method` property of `require('jayson')`. It supports these options:
653
654| Option | Default | Type | Description |
655|----------- |-------------------------------- |--------------------- |------------------------------------------------------------------------ |
656| `handler` | | `Function` | The actual function that will handle a JSON-RPC request to this method |
657| `useContext` | false | `Boolean` | When true, the handler will receive a context object as the second argument
658| `params` | null | `Array|Object|null` | Force JSON-RPC parameters to be of a certain type |
659
660Server example showcasing most features and options in [examples/method_definitions/server.js](examples/method_definitions/server.js):
661
662```javascript
663'use strict';
664
665const jayson = require('./../..');
666const _ = require('lodash');
667
668const methods = {
669
670 // this function will be wrapped in jayson.Method with options given to the server
671 sum: function(args, done) {
672 done(null, sum(args));
673 },
674
675 // this function always receives a context object as second arg
676 // it can be overriden on the server level
677 context: jayson.Method(function(args, context, done) {
678 done(null, context);
679 }, {useContext: true}),
680
681 // specifies some default values (alternate definition too)
682 sumDefault: jayson.Method(function(args, done) {
683 const total = sum(args);
684 done(null, total);
685 }, {
686 params: {a: 2, b: 5} // map of defaults
687 }),
688
689 // this method returns true when it gets an array (which it always does)
690 isArray: new jayson.Method({
691 handler: function(args, done) {
692 const result = _.isArray(args);
693 done(null, result);
694 },
695 params: Array // could also be "Object"
696 })
697
698};
699
700const server = jayson.server(methods, {
701 // these options are given as options to jayson.Method when adding the method "sum".
702 // this is because it is not wrapped in jayson.Method like the others.
703 useContext: false,
704 params: Array
705});
706
707server.http().listen(3000);
708
709// sums all numbers in an array or object
710function sum(list) {
711 return _.reduce(list, function(sum, val) {
712 return sum + val;
713 }, 0);
714}
715```
716
717Client example in [examples/method_definitions/client.js](examples/method_definitions/client.js):
718
719```javascript
720'use strict';
721
722const jayson = require('./../..');
723
724const client = jayson.client.http({
725 port: 3000
726});
727
728// invoke "sum" with array
729client.request('sum', [3, 5, 9, 11], function(err, response) {
730 if(err) throw err;
731 console.log(response.result); // 28
732});
733
734// invoke "sum" with an object
735client.request('sum', {a: 2, b: 3, c: 4}, function(err, response) {
736 if(err) throw err;
737 console.log(response.result); // 9
738});
739
740// invoke "sumDefault" with object missing some defined members
741client.request('sumDefault', {b: 10}, function(err, response) {
742 if(err) throw err;
743 console.log(response.result); // 12
744});
745
746// invoke "isArray" with an Object
747client.request('isArray', {a: 5, b: 2, c: 9}, function(err, response) {
748 if(err) throw err;
749 console.log(response.result); // true
750});
751
752// invoke "context"
753client.request('context', {hello: 'world'}, function(err, response) {
754 if(err) throw err;
755 console.log(response.result); // {} - just an empty object
756});
757```
758
759#### Server events
760
761In addition to events that are specific to certain interfaces, all servers will emit the following events:
762
763| Event | When | Arguments | Notes |
764|------------ |------------------------------------------ |-------------------------------------- |-------------------------------- |
765| `request` | Interpretable non-batch request received | 1: Request object | |
766| `response` | Returning a response | 1: Request object 2: Response object | |
767| `batch` | Interpretable batch request received | 1. Array of requests | Emits `request` for every part |
768
769#### Server Errors
770
771If 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:
772
773```javascript
774var jayson = require('jayson');
775
776var server = jayson.server({
777 i_cant_find_anything: function(args, callback) {
778 var error = {code: 404, message: 'Cannot find ' + args.id};
779 callback(error); // will return the error object as given
780 },
781 i_cant_return_a_valid_error: function(callback) {
782 callback({message: 'I forgot to enter a code'}); // will return a pre-defined "Internal Error"
783 }
784});
785```
786
787##### Predefined Errors
788
789It 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:
790
791[jsonrpc-spec#error_object]: http://jsonrpc.org/spec.html#error_object
792
793```javascript
794var jayson = require('jayson');
795
796var server = jayson.server({
797 invalid_params: function(args, callback) {
798 var error = this.error(-32602); // returns an error with the default properties set
799 callback(error);
800 }
801});
802```
803
804You can even override the default messages:
805
806```javascript
807var jayson = require('jayson');
808
809var server = jayson.server({
810 error_giver_of_doom: function(callback) {
811 callback(true) // invalid error format, which causes an Internal Error to be returned instead
812 }
813});
814
815// Override the default message
816server.errorMessages[Server.errors.INTERNAL_ERROR] = 'I has a sad. I cant do anything right';
817```
818
819#### Server CORS
820
821Jayson does not include functionality for supporting CORS requests natively but it is easy to use a CORS-enabling middleware
822like [cors](https://github.com/expressjs/cors). An example of this can be found in [examples/cors/server.js](examples/cors/server.js):
823
824```javascript
825'use strict';
826
827const jayson = require('./../..');
828const cors = require('cors');
829const connect = require('connect');
830const jsonParser = require('body-parser').json;
831const app = connect();
832
833const server = jayson.server({
834 myNameIs: function(args, callback) {
835 callback(null, 'Your name is: ' + args.name);
836 }
837});
838
839app.use(cors({methods: ['POST']}));
840app.use(jsonParser());
841app.use(server.middleware());
842
843app.listen(3000);
844```
845
846#### Server Context
847
848*Since version 3.0.0*
849
850You can provide an optional context object to JSON-RPC method handlers. This can be used to give extra data to a handler such as request headers, authentication tokens, and so on.
851
852This feature is unlocked by having `jayson.Method` accepts a boolean option called `useContext`. It always defaults to `false` for backwards compatibility. When it is set to `true` the method handler that `jayson.Method` wraps will **always** receive a context object as the second argument. The object can be given as the third argument to `jayson.Server.prototype.call`.
853
854Server example in [examples/context/server.js](examples/context/server.js):
855
856```javascript
857'use strict';
858
859const _ = require('lodash');
860const jayson = require('./../..');
861const jsonParser = require('body-parser').json;
862const express = require('express');
863const app = express();
864
865const server = jayson.server({
866
867 getHeaders: function(args, context, callback) {
868 callback(null, context.headers);
869 },
870
871 // old method not receiving a context object (here for reference)
872 oldMethod: new jayson.Method(function(args, callback) {
873 callback(null, {});
874 }, {
875 // this setting overrides the server option set below for this particular method only
876 useContext: false
877 })
878
879}, {
880 // all methods will receive a context object as the second arg
881 useContext: true
882});
883
884app.use(jsonParser());
885app.use(function(req, res, next) {
886 // prepare a context object passed into the JSON-RPC method
887 const context = {headers: req.headers};
888 server.call(req.body, context, function(err, result) {
889 if(err) return next(err);
890 res.send(result || {});
891 });
892});
893
894app.listen(3001);
895```
896
897Client example in [examples/context/client.js](examples/context/client.js):
898
899```javascript
900'use strict';
901
902const jayson = require('./../..');
903
904// create a client
905const client = jayson.client.http({
906 port: 3001
907});
908
909// invoke "getHeaders"
910client.request('getHeaders', {}, function(err, response) {
911 if(err) throw err;
912 console.log(response.result);
913});
914```
915
916##### Notes
917
918- `jayson.Server` also accepts `useContext` as an option, and passes the value on to the `jayson.Method` constructor. This option can be overriden on a per-method basis as shown above.
919- Individual requests in a JSON-RPC batch will all receive the exact same context object in their handler - take care not to mutate it
920- If a falsy context value is given to `jayson.Server.prototype.call`, an empty object will be created
921- None of the current jayson server transports (http, https, tls, tcp, middleware) can make use of the context object. You will need to rig your own transport implementation, like the one above based on an `express` http server. See the [FAQ](#faq) for more info about this.
922
923### Revivers and Replacers
924
925JSON 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.
926
927Simple example transferring the state of an object between a client and a server:
928
929Shared code between the server and the client in [examples/reviving_and_replacing/shared.js](examples/reviving_and_replacing/shared.js):
930
931```javascript
932'use strict';
933
934const Counter = exports.Counter = function(value) {
935 this.count = value || 0;
936};
937
938Counter.prototype.increment = function() {
939 this.count += 1;
940};
941
942exports.replacer = function(key, value) {
943 if(value instanceof Counter) {
944 return {$class: 'counter', $props: {count: value.count}};
945 }
946 return value;
947};
948
949exports.reviver = function(key, value) {
950 if(value && value.$class === 'counter') {
951 const obj = new Counter();
952 for(const prop in value.$props) obj[prop] = value.$props[prop];
953 return obj;
954 }
955 return value;
956};
957```
958
959Server example in [examples/reviving_and_replacing/server.js](examples/reviving_and_replacing/server.js):
960
961```javascript
962'use strict';
963
964const jayson = require('./../..');
965const shared = require('./shared');
966
967// Set the reviver/replacer options
968const options = {
969 reviver: shared.reviver,
970 replacer: shared.replacer
971};
972
973// create a server
974const server = jayson.server({
975 increment: function(args, callback) {
976 args.counter.increment();
977 callback(null, args.counter);
978 }
979}, options);
980
981server.http().listen(3000);
982```
983
984A client example in [examples/reviving_and_replacing/client.js](examples/reviving_and_replacing/client.js) invoking "increment" on the server:
985
986```javascript
987'use strict';
988
989const jayson = require('./../..');
990const shared = require('./shared');
991
992const client = jayson.client.http({
993 port: 3000,
994 reviver: shared.reviver,
995 replacer: shared.replacer
996});
997
998// create the object
999const params = {
1000 counter: new shared.Counter(2)
1001};
1002
1003// invoke "increment"
1004client.request('increment', params, function(err, response) {
1005 if(err) throw err;
1006 const result = response.result;
1007 console.log(
1008 result instanceof shared.Counter, // true
1009 result.count, // 3
1010 params.counter === result // false - result is a new object
1011 );
1012});
1013```
1014
1015#### Notes
1016
1017* 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.
1018
1019### Named parameters
1020
1021It is possible to specify named parameters when doing a client request by passing an Object instead of an Array.
1022
1023Client example in [examples/named_parameters/client.js](examples/named_parameters/client.js):
1024
1025```javascript
1026'use strict';
1027
1028const jayson = require('jayson');
1029
1030const client = jayson.client.http({
1031 port: 3000
1032});
1033
1034client.request('add', {b: 1, a: 2}, function(err, response) {
1035 if(err) throw err;
1036 console.log(response.result); // 3!
1037});
1038```
1039
1040Server example in [examples/named_parameters/server.js](examples/named_parameters/server.js):
1041
1042```javascript
1043'use strict';
1044
1045const jayson = require('./../..');
1046
1047const server = jayson.server({
1048 add: function(params, callback) {
1049 callback(null, params.a + params.b);
1050 }
1051});
1052
1053server.http().listen(3000);
1054```
1055
1056#### Notes
1057
1058* If requesting methods on a Jayson server, arguments left out will be `undefined`
1059* Too many arguments or arguments with invalid names will be ignored
1060* It is assumed that the last argument to a server method is the callback and it will not be filled with something else
1061* Parsing a function signature and filling in arguments is generally *not recommended* and should be avoided
1062
1063## Promises
1064
1065[es6-promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
1066
1067*Since version 2.0.0*
1068
1069A 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:
1070
1071* Returning a Promise when requesting a JSON-RPC method using a Client
1072* Returning a Promise inside of a Server method
1073
1074To use the separate tree, do a `require('jayson/promise')` instead of `require('jayson')`.
1075
1076Server example in [examples/promise/server.js](examples/promise/server.js) showing how to return a `Promise` in a server method:
1077
1078```javascript
1079'use strict';
1080
1081const jayson = require('../../promise');
1082const _ = require('lodash');
1083
1084const server = jayson.server({
1085
1086 add: function(args) {
1087 return new Promise(function(resolve, reject) {
1088 const sum = _.reduce(args, function(sum, value) { return sum + value; }, 0);
1089 resolve(sum);
1090 });
1091 },
1092
1093 // example on how to reject
1094 rejection: function(args) {
1095 return new Promise(function(resolve, reject) {
1096 // server.error just returns {code: 501, message: 'not implemented'}
1097 reject(server.error(501, 'not implemented'));
1098 });
1099 }
1100
1101});
1102
1103server.http().listen(3000);
1104```
1105
1106Client example in [examples/promise/client.js](examples/promise/client.js) showing how to do a request:
1107
1108```javascript
1109'use strict';
1110
1111const jayson = require('../../promise');
1112
1113const client = jayson.client.http({
1114 port: 3000
1115});
1116
1117const reqs = [
1118 client.request('add', [1, 2, 3, 4, 5]),
1119 client.request('rejection', [])
1120];
1121
1122Promise.all(reqs).then(function(responses) {
1123 console.log(responses[0].result);
1124 console.log(responses[1].error);
1125});
1126```
1127
1128#### Notes
1129
1130* 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.
1131* A `Promise` is considered to have been returned from a server method if the returned object has a property `then` that is a function.
1132
1133#### Promise Batches
1134
1135*Since version 2.0.5*
1136
1137Sometimes 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.
1138
1139To solve this, we need to set the fourth parameter to `PromiseClient.prototype.request` explicitly to `false` in order to *not* return a Promise.
1140
1141Client example in [examples/promise_batches/client.js](examples/promise_batches/client.js) showing how to properly execute a batch request:
1142
1143```javascript
1144'use strict';
1145
1146const jayson = require('../../promise');
1147
1148const client = jayson.client.http({
1149 port: 3000
1150});
1151
1152const batch = [
1153 client.request('add', [1, 2, 3, 4, 5], undefined, false),
1154 client.request('add', [5, 6, 7, 8, 9], undefined, false),
1155];
1156
1157client.request(batch).then(function(responses) {
1158 console.log(responses[0].result); // 15
1159 console.log(responses[1].result); // 35
1160});
1161```
1162
1163##### Notes
1164
1165* 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.
1166
1167## FAQ
1168
1169### How can I pass HTTP headers/session/etc into my JSON-RPC request handler?
1170
1171*Support for method context added in version 3.0.0*
1172
1173See [Server context](#server-context) section.
1174
1175### What is the recommended way to use jayson?
1176
1177Using the provided clients and servers for http, https, tls, tcp and the express middleware is fine and works well for most use cases. However, sometimes issues like these crop up (quotes below are not directly from issue posters):
1178
1179- "The (non-jayson) HTTP/TCP server I'm interacting with expects every call to terminate with `\r\n` but the jayson client does not"
1180- ["How can my jayson TLS server support requests encoded such and such?"](https://github.com/tedeh/jayson/issues/86)
1181- ["How can I make the jayson HTTP middleware accept GET requests?"](https://github.com/tedeh/jayson/issues/70)
1182- ["My jayson client interacting with a (non-jayson) TLS server closes the connection after every sent request. I think this is wasteful!"](https://github.com/tedeh/jayson/issues/92)
1183
1184These are not issues with jayson, but stem from the fact that [JSON-RPC 2.0 specification][jsonrpc-spec] is **transport agnostic** and these kind of behaviours are **not defined** by that specification. The clients provided by jayson for http, https, tls, tcp are made to work and tested with their corresponding jayson server implementation. Any other compatibility with any other server or client is *accidental* when it comes to **details of the transport layer**. With that said, jayson is made to be 100 % compatible with the [JSON-RPC 2.0 specification][jsonrpc-spec] and compatibility with other non-jayson servers or clients when it comes to the *application layer* is pretty much guaranteed.
1185
1186The library author [tedeh](https://github.com/tedeh) therefore recommends that if you have particular needs when it comes to the transport layer you create an implementation satisfying these details yourself. **Doing this is actually quite simple.**
1187
1188Example of a http server built with express in [examples/faq_recommended_http_server/server.js](examples/faq_recommended_http_server/server.js):
1189
1190```javascript
1191'use strict';
1192
1193const _ = require('lodash');
1194const jayson = require('./../..');
1195const jsonParser = require('body-parser').json;
1196const express = require('express');
1197const app = express();
1198
1199// create a plain jayson server
1200const server = jayson.server({
1201 add: function(numbers, callback) {
1202 callback(null, _.reduce(numbers, (sum, val) => sum + val, 0));
1203 }
1204});
1205
1206app.use(jsonParser()); // <- here we can deal with maximum body sizes, etc
1207app.use(function(req, res, next) {
1208 const request = req.body;
1209 // <- here we can check headers, modify the request, do logging, etc
1210 server.call(request, function(err, response) {
1211 if(err) {
1212 // if err is an Error, err is NOT a json-rpc error
1213 if(err instanceof Error) return next(err);
1214 // <- deal with json-rpc errors here, typically caused by the user
1215 res.status(400);
1216 res.send(err);
1217 return;
1218 }
1219 // <- here we can mutate the response, set response headers, etc
1220 if(response) {
1221 res.send(response);
1222 } else {
1223 // empty response (could be a notification)
1224 res.status(204);
1225 res.send('');
1226 }
1227 });
1228});
1229
1230app.listen(3001);
1231```
1232
1233Using some of the utilities provided and exported by jayson, creating a client offering the same kind of flexibility is also simple. Example of a compatible http client built with superagent in [examples/faq_recommended_http_server/client.js](examples/faq_recommended_http_server/client.js):
1234
1235```javascript
1236'use strict';
1237
1238const jayson = require('./../..');
1239const request = require('superagent');
1240
1241// generate a json-rpc version 2 compatible request (non-notification)
1242const requestBody = jayson.Utils.request('add', [1,2,3,4], undefined, {
1243 version: 2, // generate a version 2 request
1244});
1245
1246request.post('http://localhost:3001')
1247 // <- here we can setup timeouts, set headers, cookies, etc
1248 .timeout({response: 5000, deadline: 60000})
1249 .send(requestBody)
1250 .end(function(err, response) {
1251 if(err) {
1252 // superagent considers 300-499 status codes to be errors
1253 // @see http://visionmedia.github.io/superagent/#error-handling
1254 if(!err.status) throw err;
1255 const body = err.response.body;
1256 // body may be a JSON-RPC error, or something completely different
1257 // it can be handled here
1258 if(body && body.error && jayson.Utils.Response.isValidError(body.error, 2)) {
1259 // the error body was a valid JSON-RPC version 2
1260 // we may wish to deal with it differently
1261 console.err(body.error);
1262 return;
1263 }
1264 throw err; // error was something completely different
1265 }
1266
1267 const body = response.body;
1268
1269 // check if we got a valid JSON-RPC 2.0 response
1270 if(!jayson.Utils.Response.isValidResponse(body, 2)) {
1271 console.err(body);
1272 }
1273
1274 if(body.error) {
1275 // we have a json-rpc error...
1276 console.err(body.error); // 10!
1277 } else {
1278 // do something useful with the result
1279 console.log(body.result); // 10!
1280 }
1281 });
1282```
1283
1284## Contributing
1285
1286Highlighting [issues](https://github.com/tedeh/jayson/issues) or submitting pull
1287requests on [Github](https://github.com/tedeh/jayson) is most welcome.
1288
1289Please make sure to follow the style of the project, and lint your code with `npm run lint` before submitting a patch.