UNPKG

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