UNPKG

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