Giter Site home page Giter Site logo

jayson's Introduction

Jayson

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

Coverage Status GitHub issues Dependents (via libraries.io) node-current Libraries.io dependency status for latest release npm version npm npm bundle size npm bundle size Known Vulnerabilities

Table of contents

Features

Example

A basic JSON-RPC 2.0 server via HTTP:

Server example in examples/simple_example/server.js:

const jayson = require('jayson');

// create a server
const server = new jayson.Server({
  add: function(args, callback) {
    callback(null, args[0] + args[1]);
  }
});

server.http().listen(3000);

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

const jayson = require('jayson');

// create a client
const client = jayson.Client.http({
  port: 3000
});

// invoke "add"
client.request('add', [1, 1], function(err, response) {
  if(err) throw err;
  console.log(response.result); // 2
});

Installation

Install the latest version of jayson from npm by executing npm install jayson in your shell. Do a global install with npm install --global jayson if you want the jayson client CLI in your PATH.

Changelog (only notable milestones/changes)

  • 4.1.0
    • New server option maxBatchLength
  • 4.0.0
    • Remove lodash dependency which should halve bundle size. There might be minor incompatibilities if you pass funky object or array types to jayson methods.
  • 3.6.4
    • Websocket client and server support
  • 3.6.1
    • JSON-RPC 2.0 notifications no longer have id property unless overridden
  • 3.3.3
    • Promise support for browser client
    • TypeScript declaration for promise browser client
    • TypeScript declaration for browser client
  • 3.3.0
    • Remove URL parsing when passing a string option to the TLS and TCP client, string options are instead treated as an IPC path
  • 3.0.0
    • Can pass a context object to handlers
    • Breaking: collect option removed from jayson.Server/Method. JSON-RPC params to handlers are now always in the first argument.
  • 2.1.0
    • Experimental typescript support
  • 2.0.6
  • 2.0.0
    • Added support for promises
    • Breaking: collect: true is now the default option for a new jayson.Server and jayson.Method
  • 1.2.0
  • 1.1.1
    • More http server events
    • Remove fork server and client
    • Add server routing
  • 1.0.11 Add support for a HTTPS client
  • 1.0.9 Add support for TCP servers and clients

CLI client

There is a 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.

Requirements

Jayson does not have any special dependencies that cannot be resolved with a simple npm install or yarn install.

Class documentation

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

Running tests

  • Change directory to the repository root
  • Install the development packages by executing npm install --dev
  • Run the tests with npm run test
  • Run the typescript tests with npm run test-tsc
  • Run the coverage tests with npm run coverage

Typescript

Since v2.1.0 there is typescript support available with jayson.

If you encounter any problems with the type definitions, see the Contributing section.

Usage

Client

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

Client interface description

Name Description
Client Base class
Client.tcp TCP sub-class
Client.tls TLS sub-class
Client.http HTTP sub-class
Client.https HTTPS sub-class
Client.browser Standalone class
Client.websocket Websocket sub-class

Every client supports these options:

Option Default Type Description
reviver undefined Function JSON.parse reviver
replacer undefined Function JSON.stringify replacer
generator RFC4122 generator Function Generates a String for request ID.
version 2 Number JSON-RPC version to support (1 or 2)
notificationIdNull false Boolean Since 3.6.1. When true "id" property of a request will be set to null when version 2.
Client.http

Uses the same options as http.request in addition to these options:

Option Default Type Description
encoding utf8 String Determines the encoding to use
headers undefined Object Extend the headers sent by the client
Client.http Events

The HTTP client will emit the following events:

Event When Arguments Notes
http request Created an HTTP request 1. Instance of http.ClientRequest
http response Received an HTTP response 1. Instance of http.IncomingMessage 2. Instance of http.ClientRequest
http error Underlying stream emits error 1. Error
http timeout Underlying stream emits timeout Automatically causes the request to abort

It is possible to pass a string URL as the first argument. The URL will be run through url.parse. Example:

const jayson = require('jayson');
const client = jayson.Client.http('http://localhost:3000');
// client.options is now the result of url.parse
Client.https

Uses the same options as https.request in addition to the same options as Client.http. This means it is also possible to pass a string URL as the first argument and have it interpreted by url.parse.

Will emit the same custom events as Client.http.

Client.tcp

Uses the same options as net.connect.

Client.tcp Events

Since version 3.5.1

The TCP client will emit the following events:

Event When Arguments Notes
tcp socket TCP socket is opened 1. net.Socket Can be used to setup timeouts
tcp error TCP socket emits error 1. Error emit by net.Socket
Client.tls

Uses the same options as tls.connect.

Client.tls Events

Since version 3.5.1

The TLS client will emit the following events:

Event When Arguments Notes
tcp socket TCP socket is opened 1. net.Socket Can be used to setup timeouts
tcp error TCP socket emits error 1. Error emit by net.Socket
Client.browser

The 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 package for generating request ids. It also does not know how to "send" a request to a server like the other clients.

Because 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.

To 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).

The reason for dealing with strings is to support the reviver and replacer options like the other clients.

This client example in examples/browser_client/client.js below uses node-fetch in the transport function, but a dropin replacement for use in an actual browser could instead use whatwg-fetch.

The browser client has a separate TypeScript type declaration available in jayson/lib/client/browser/index.d.ts which depends on the main Jayson type declaration.

'use strict';

const jaysonBrowserClient = require('jayson/lib/client/browser');
const fetch = require('node-fetch');

const callServer = function(request, callback) {
  const options = {
    method: 'POST',
    body: request,
    headers: {
      'Content-Type': 'application/json',
    }
  };

  fetch('http://localhost:3000', options)
    .then(function(res) { return res.text(); })
    .then(function(text) { callback(null, text); })
    .catch(function(err) { callback(err); });
};

const client = new jaysonBrowserClient(callServer, {
  // other options go here
});

client.request('multiply', [5, 5], function(err, error, result) {
  if(err) throw err;
  console.log(result); // 25
});
Client.websocket

Since v3.6.4

Experimental websocket client that wraps around an isomorphic-ws instance. Will listen to every received (JSON) message and see if it matches any of the currently outstanding requests made, in which case the callback of that outstanding request will fire. If you do not provide the timeout option it will wait forever. Has a promise-based equivalent receiving the same options, and a companion jayson server where you can find an example.

Has the following options:

Option Default Type Description
url undefined String First argument to require('isomorphic-ws') if options.ws not set
ws undefined require('isomorphic-ws') instance WebSocket instance
timeout undefined Number Timeout in ms before callbacking with an error

If you want to "unwrap" the isomorphic-ws instance you can use the Client.websocket.prototype.unlisten which stops listening for messages on the isomorphic-ws instance.

Notifications

Notification requests are for cases where the reply from the server is not important and should be ignored. This is accomplished by setting the id property of a request object to null.

Client example in examples/notifications/client.js doing a notification request:

const jayson = require('jayson');

const client = jayson.Client.http({
  port: 3000
});

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

Server example in examples/notifications/server.js:

const jayson = require('jayson');

const server = new jayson.Server({
  ping: function(args, callback) {
    // do something, do nothing
    callback();
  }
});

server.http().listen(3000);
Notes
  • Any value that the server returns will be discarded when doing a notification request.
  • Omitting the third argument null to Client.prototype.request does not generate a notification request. This argument has to be set explicitly to null for this to happen.
  • Network errors and the like will still reach the callback. When the callback is invoked (with or without error) one can be certain that the server has received the request.
  • See the Official JSON-RPC 2.0 Specification for additional information on how Jayson handles notifications that are erroneous.
  • Since 3.6.1 When making a JSON-RPC 2.0 notification request the "id" property will be omitted in the request object. In previous versions it was set to null against the recommendation of the official specification. This behaviour can be overridden with the notificationIdNull option.

Batches

A batch request is an array of individual requests that are sent to the server as one. Doing a batch request is very simple in Jayson and consists of constructing an array of individual requests (created by not passing a callback to Client.prototype.request) that is then itself passed to Client.prototype.request.

Combined server/client example in examples/batch_request/index.js:

const jayson = require('jayson');

const server = new jayson.Server({
  add: function(args, callback) {
    callback(null, args[0] + args[1]);
  }
});

const client = new jayson.Client(server);

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

client.request(batch, function(err, errors, successes) {
  if(err) throw err;
  console.log('errors', errors); // array of requests that errored
  console.log('successes', successes); // array of requests that succeeded
});

client.request(batch, function(err, responses) {
  if(err) throw err;
  console.log('responses', responses); // all responses together
});
Notes
  • See the Official JSON-RPC 2.0 Specification for additional information on how Jayson handles different types of batches, mainly with regards to notifications, request errors and so forth.
  • There is no guarantee that the results will be in the same order as request Array request. To find the right result, compare the ID from the request with the ID in the result yourself.

Client callback syntactic sugar

When the length (number of arguments) of a client callback function is either 2 or 3 it receives slightly different values when invoked.

  • 2 arguments: first argument is an error or null, second argument is the response object as returned (containing either a result or a error property) or null for notifications.
  • 3 arguments: first argument is an error or null, second argument is a JSON-RPC error property or null (if success), third argument is a JSON-RPC result property or null (if error).

When doing a batch request with a 3-length callback, the second argument will be an array of requests with a error property and the third argument will be an array of requests with a result property.

Client events

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

Event When Arguments Notes
request About to dispatch a request 1: Request object
response Received a response 1: Request object 2: Response object received

Server

The server classes are available as the Server or server property of require('jayson').

The server also sports several interfaces that can be accessed as properties of an instance of Server.

Server interface description

Name Description
Server Base interface for a server that supports receiving JSON-RPC requests
Server.tcp TCP server that inherits from net.Server
Server.tls TLS server that inherits from tls.Server
Server.http HTTP server that inherits from http.Server
Server.https HTTPS server that inherits from https.Server
Server.websocket Websocket server that uses isomorphic-ws Server
Server.middleware Method that returns a Connect/Express compatible middleware function

Servers supports these options:

Option Default Type Description
reviver null Function JSON.parse reviver
replacer null Function JSON.stringify replacer
router null Function Return the function for method routing
useContext false Boolean Passed to methodConstructor options
params undefined Array/Object/null Passed to methodConstructor options
methodConstructor jayson.Method Function Server functions are made an instance of this class
version 2 Number JSON-RPC version to support (1 or 2)
maxBatchLength Infinity Number Maximum batch requests allowed
Server.tcp

Uses the same options as the base class. Inherits from net.Server.

Server.tls

Uses the same options as the base class. Inherits from tls.Server.

Server.http

Uses the same options as the base class. Inherits from http.Server.

Server.http Events
Event When Arguments Notes
http request Incoming HTTP request 1. Instance of http.IncomingMessage
http response About to send a HTTP response 1. Instance of http.ServerResponse 2. Instance of http. IncomingMessage
Server.https

Uses the same options as the base class. Inherits from https.Server and jayson.Server.http. For information on how to configure certificates, see the documentation on https.Server.

Will emit the same custom events as Server.http.

Server.middleware

Uses the same options as the base class. Returns a function that is compatible with Connect or Express. Will expect the request to be req.body, meaning that the request body must be parsed (typically using connect.bodyParser) before the middleware is invoked.

The middleware supports the following options:

Option Default Type Description
end true Boolean If set to false causes the middleware to next() instead of res.end() when finished.

Middleware example in examples/middleware/server.js:

const jayson = require('jayson');
const jsonParser = require('body-parser').json;
const connect = require('connect');
const app = connect();

const server = new jayson.Server({
  add: function(args, callback) {
    callback(null, args[0] + args[1]);
  }
});

// parse request body before the jayson middleware
app.use(jsonParser());
app.use(server.middleware());

app.listen(3000);
Server.websocket

Websocket server that either wraps around a provided require('isomorphic-ws').Server instance or creates one from scratch. Expects every incoming message on every connection to be a valid JSON-RPC call.

The websocket server supports the following options in addition to the base class:

Option Default Type Description
wss undefined require('isomorphic-ws').Server If not provided will be created

Websocket server example in examples/websocket/server.js:

const jayson = require('jayson');

const server = new jayson.Server({
  add: function (args, done) {
    const sum = args.reduce((sum, val) => sum + val, 0);
    done(null, sum);
  },
});

const wss = server.websocket({
  port: 12345,
});

Websocket client example in examples/websocket/client.js:

const jayson = require('jayson');

const client = jayson.Client.websocket({
  url: 'ws://localhost:12345',
});

client.ws.on('open', function () {
  client.request('add', [1,2,3,4], function (err, result) {
    console.log(err, result);
    client.ws.close();
  });
});

Many interfaces at the same time

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

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

const jayson = require('jayson');

const server = new jayson.Server();

// "http" will be an instance of require('http').Server
const http = server.http();

// "https" will be an instance of require('https').Server
const https = server.https({
  //cert: require('fs').readFileSync('cert.pem'),
  //key require('fs').readFileSync('key.pem')
});

http.listen(80, function() {
  console.log('Listening on *:80');
});

https.listen(443, function() {
  console.log('Listening on *:443');
});

Using the server as a relay

Passing an instance of a client as a method to the server makes the server relay incoming requests to wherever the client is pointing to. This might be used to delegate computationally expensive functions into a separate server or to abstract a cluster of servers behind a common interface.

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

const jayson = require('jayson');

// create a server where "add" will relay a localhost-only server
const server = new jayson.Server({
  add: jayson.Client.http({
    port: 3001
  })
});

// let the frontend server listen to *:3000
server.http().listen(3000);

Backend server example in examples/relay/server_private.js listening on *:3001:

const jayson = require('jayson');

const server = new jayson.Server({
  add: function(args, callback) {
    callback(null, args[0] + args[1]);
  }
});

// let the backend listen to *:3001
server.http().listen(3001);

Every request to add on the public server will now relay the request to the private server. See the client example in examples/relay/client.js.

Method routing

Passing a property named router in the server options will enable you to write your own logic for routing requests to specific functions.

Server example with custom routing logic in examples/method_routing/server.js:

Note: You are strongly recommended to check method names using Object.prototype.hasOwnProperty to prevent a denial-of-service attack against your Jayson server when names such as __defineGetter__ are used in JSON-RPC calls.

const jayson = require('jayson');

const methods = {
  add: function(args, callback) {
    callback(null, args[0] + args[1]);
  }
};

const server = new jayson.Server(methods, {
  router: function(method, params) {
    // regular by-name routing first
    const fn = this._methods.hasOwnProperty(method) ? this._methods[method] : null;
    if(typeof fn === 'function') {
      return fn;
    }
    if(method === 'add_2') {
      const fn = server.getMethod('add').getHandler();
      return new jayson.Method(function(args, done) {
        args.unshift(2);
        fn(args, done);
      });
    }
  }
});

server.http().listen(3000);

Client example in examples/method_routing/client.js invoking add_2 on the above server:

const jayson = require('jayson');

// create a client
const client = jayson.Client.http({
  port: 3000
});

// invoke "add_2"
client.request('add_2', [3], function(err, response) {
  if(err) throw err;
  console.log(response.result); // 5!
});

Server example of nested routes where each property is separated by a dot (you do not need to use the router option for this):

const jayson = require('jayson');

const methods = {
  foo: {
    bar: function(callback) {
      callback(null, 'ping pong');
    }
  },
  math: {
    add: function(args, callback) {
      callback(null, args[0] + args[1]);
    }
  }
};

// this reduction produces an object like this: {'foo.bar': [Function], 'math.add': [Function]}
const map = Object.keys(methods).reduce(collapse('', '.', methods), {});
const server = new jayson.Server(map);

function collapse(stem, sep, obj) {
  return function(map, key) {
    const prop = stem ? stem + sep + key : key;
    const value = obj[key];
    if(typeof value === 'function') map[prop] = value;
    else if(typeof value === 'object' && value !== null) map = Object.keys(value).reduce(collapse(prop, sep, value), map);
    return map;
  }
}
Notes
  • If router does not return anything, the server will respond with a Method Not Found error.
  • The Server.prototype methods method, methods, removeMethod and hasMethod will not use the router method, but will operate on the internal Server.prototype._methods map.
  • The router method is expected to return instances of jayson.Method (>=1.2.0)

Method definition

You can also define server methods inside a wrapping object named jayson.Method. This allows additional options about the method to be specified. Using this wrapper - explicitly or implicitly (via server options) - makes it trivial to have your method accept a variable amount of arguments.

The method class is available as the Method or method property of require('jayson'). It supports these options:

Option Default Type Description
handler Function The actual function that will handle a JSON-RPC request to this method
useContext false Boolean When true, the handler will receive a context object as the second argument
params null Array|Object|null Force JSON-RPC parameters to be of a certain type

Server example showcasing most features and options in examples/method_definitions/server.js:

const jayson = require('jayson');

const methods = {

  // this function will be wrapped in jayson.Method with options given to the server
  sum: function(args, done) {
    done(null, sum(args));
  },

  // this function always receives a context object as second arg
  // it can be overriden on the server level
  context: jayson.Method(function(args, context, done) {
    done(null, context);
  }, {useContext: true}),

  // specifies some default values (alternate definition too)
  sumDefault: jayson.Method(function(args, done) {
    const total = sum(args);
    done(null, total);
  }, {
    params: {a: 2, b: 5} // map of defaults
  }),

  // this method returns true when it gets an array (which it always does)
  isArray: new jayson.Method({
    handler: function(args, done) {
      const result = Array.isArray(args);
      done(null, result);
    },
    params: Array // could also be "Object"
  })

};

const server = new jayson.Server(methods, {
  // these options are given as options to jayson.Method when adding the method "sum".
  // this is because it is not wrapped in jayson.Method like the others.
  useContext: false,
  params: Array
});

server.http().listen(3000);

// sums all numbers in an array or object
function sum(list) {
  return Object.keys(list).reduce(function(sum, key) { return sum + list[key]; }, 0);
}

Client example in examples/method_definitions/client.js:

const jayson = require('jayson');

const client = jayson.Client.http({
  port: 3000
});

// invoke "sum" with array
client.request('sum', [3, 5, 9, 11], function(err, response) {
  if(err) throw err;
  console.log(response.result); // 28
});

// invoke "sum" with an object
client.request('sum', {a: 2, b: 3, c: 4}, function(err, response) {
  if(err) throw err;
  console.log(response.result); // 9
});

// invoke "sumDefault" with object missing some defined members
client.request('sumDefault', {b: 10}, function(err, response) {
  if(err) throw err;
  console.log(response.result); // 12
});

// invoke "isArray" with an Object
client.request('isArray', {a: 5, b: 2, c: 9}, function(err, response) {
  if(err) throw err;
  console.log(response.result); // true
});

// invoke "context"
client.request('context', {hello: 'world'}, function(err, response) {
  if(err) throw err;
  console.log(response.result); // {} - just an empty object
});

Server events

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

Event When Arguments Notes
request Interpretable non-batch request received 1: Request object
response Returning a response 1: Request object 2: Response object
batch Interpretable batch request received 1. Array of requests Emits request for every part

Server Errors

If you should like to return an error from an method request to indicate a failure, remember that the JSON-RPC 2.0 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:

const jayson = require('jayson');

const server = new jayson.Server({
  i_cant_find_anything: function(args, callback) {
    const error = {code: 404, message: 'Cannot find ' + args.id};
    callback(error); // will return the error object as given
  },
  i_cant_return_a_valid_error: function(callback) {
    callback({message: 'I forgot to enter a code'}); // will return a pre-defined "Internal Error"
  }
});
Predefined Errors

It is also possible to cause a method to return one of the predefined JSON-RPC 2.0 error codes using the server helper function Server.prototype.error inside of a server method. Example:

const jayson = require('jayson');

const server = new jayson.Server({
  invalid_params: function(args, callback) {
    const error = this.error(-32602); // returns an error with the default properties set
    callback(error);
  }
});

You can even override the default messages:

const jayson = require('jayson');

const server = new jayson.Server({
  error_giver_of_doom: function(callback) {
    callback(true) // invalid error format, which causes an Internal Error to be returned instead
  }
});

// Override the default message
server.errorMessages[Server.errors.INTERNAL_ERROR] = 'I has a sad. I cant do anything right';

Server CORS

Jayson does not include functionality for supporting CORS requests natively but it is easy to use a CORS-enabling middleware like cors. An example of this can be found in examples/cors/server.js:

const jayson = require('jayson');
const cors = require('cors');
const connect = require('connect');
const jsonParser = require('body-parser').json;
const app = connect();

const server = new jayson.Server({
  myNameIs: function(args, callback) {
    callback(null, 'Your name is: ' + args.name);
  }
});

app.use(cors({methods: ['POST']}));
app.use(jsonParser());
app.use(server.middleware());

app.listen(3000);

Server Context

Since version 3.0.0

You 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.

This 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.

Server example in examples/context/server.js:

const jayson = require('jayson');
const jsonParser = require('body-parser').json;
const express = require('express');
const app = express();

const server = new jayson.Server({

  getHeaders: function(args, context, callback) {
    callback(null, context.headers);
  },

  // old method not receiving a context object (here for reference)
  oldMethod: new jayson.Method(function(args, callback) {
    callback(null, {});
  }, {
    // this setting overrides the server option set below for this particular method only
    useContext: false
  })

}, {
  // all methods will receive a context object as the second arg
  useContext: true
});

app.use(jsonParser());
app.use(function(req, res, next) {
  // prepare a context object passed into the JSON-RPC method
  const context = {headers: req.headers};
  server.call(req.body, context, function(err, result) {
    if(err) return next(err);
    res.send(result || {});
  });
});

app.listen(3001);

Client example in examples/context/client.js:

const jayson = require('jayson');

// create a client
const client = jayson.Client.http({
  port: 3001
});

// invoke "getHeaders"
client.request('getHeaders', {}, function(err, response) {
  if(err) throw err;
  console.log(response.result);
});
Notes
  • 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.
  • Individual requests in a JSON-RPC batch will all receive the exact same context object in their handler - take care not to mutate it
  • If a falsy context value is given to jayson.Server.prototype.call, an empty object will be created
  • 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 for more info about this.

Revivers and Replacers

JSON lacks support for representing types other than the simple ones defined in the JSON specification. Fortunately the JSON methods in JavaScript (JSON.parse and JSON.stringify) provide options for custom serialization/deserialization routines. Jayson allows you to pass your own routines as options to both clients and servers.

Simple example transferring the state of an object between a client and a server:

Shared code between the server and the client in examples/reviving_and_replacing/shared.js:

'use strict';

const Counter = exports.Counter = function(value) {
  this.count = value || 0;
};

Counter.prototype.increment = function() {
  this.count += 1;
};

exports.replacer = function(key, value) {
  if(value instanceof Counter) {
    return {$class: 'counter', $props: {count: value.count}};
  }
  return value;
};

exports.reviver = function(key, value) {
  if(value && value.$class === 'counter') {
    const obj = new Counter();
    for(const prop in value.$props) obj[prop] = value.$props[prop];
    return obj;
  }
  return value;
};

Server example in examples/reviving_and_replacing/server.js:

const jayson = require('jayson');
const shared = require('./shared');

// Set the reviver/replacer options
const options = {
  reviver: shared.reviver,
  replacer: shared.replacer
};

// create a server
const server = new jayson.Server({
  increment: function(args, callback) {
    args.counter.increment();
    callback(null, args.counter);
  }
}, options);

server.http().listen(3000);

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

const jayson = require('jayson');
const shared = require('./shared');

const client = jayson.Client.http({
  port: 3000,
  reviver: shared.reviver,
  replacer: shared.replacer
});

// create the object
const params = {
  counter: new shared.Counter(2)
};

// invoke "increment"
client.request('increment', params, function(err, response) {
  if(err) throw err;
  const result = response.result;
  console.log(
    result instanceof shared.Counter, // true
    result.count, // 3
    params.counter === result // false - result is a new object
  );
});

Notes

  • Instead of using a replacer, it is possible to define a toJSON method for any JavaScript object. Unfortunately there is no corresponding method for reviving objects (that would not work, obviously), so the reviver always has to be set up manually.

Named parameters

It is possible to specify named parameters when doing a client request by passing an Object instead of an Array.

Client example in examples/named_parameters/client.js:

const jayson = require('jayson');

const client = jayson.Client.http({
  port: 3000
});

client.request('add', {b: 1, a: 2}, function(err, response) {
  if(err) throw err;
  console.log(response.result); // 3!
});

Server example in examples/named_parameters/server.js:

const jayson = require('jayson');

const server = new jayson.Server({
  add: function(params, callback) {
    callback(null, params.a + params.b);
  }
});

server.http().listen(3000);

Notes

  • If requesting methods on a Jayson server, arguments left out will be undefined
  • Too many arguments or arguments with invalid names will be ignored
  • It is assumed that the last argument to a server method is the callback and it will not be filled with something else
  • Parsing a function signature and filling in arguments is generally not recommended and should be avoided

Promises

Since version 2.0.0

A separate tree that does limited usage of the ES6 Promise object is available. The internal API remains callback based, with the addition that promises may be used for two things:

  • Returning a Promise when requesting a JSON-RPC method using a Client
  • Returning a Promise inside of a Server method

To use the separate tree, do a require('jayson/promise') instead of require('jayson').

Server example in examples/promise/server.js showing how to return a Promise in a server method:

const jayson = require('jayson/promise');

const server = new jayson.Server({

  add: async function(args) {
    const sum = Object.keys(args).reduce(function(sum, key) { return sum + args[key]; }, 0);
    return sum;
  },

  // example on how to reject
  rejection: async function(args) {
    // server.error just returns {code: 501, message: 'not implemented'}
    throw server.error(501, 'not implemented');
  }

});

server.http().listen(3000);

Client example in examples/promise/client.js showing how to do a request:

const jayson = require('jayson/promise');

const client = jayson.Client.http({
  port: 3000
});

const reqs = [
  client.request('add', [1, 2, 3, 4, 5]),
  client.request('rejection', [])
];

Promise.all(reqs).then(function(responses) {
  console.log(responses[0].result);
  console.log(responses[1].error);
});

Notes

  • JSON-RPC errors will not result in rejection of the Promise. It is however possible that a future version will include a client setting to have JSON-RPC errors result in rejection. Please note that network errors and the like will result in rejection.
  • A Promise is considered to have been returned from a server method if the returned object has a property then that is a function.

Promise Batches

Since version 2.0.5

Sometimes you may want to return raw requests from a promise client. This needs to be handled differently, because PromiseClient.prototype.request would normally always be expected to return a Promise which we in this case don't want.

To solve this, we need to set the fourth parameter to PromiseClient.prototype.request explicitly to false in order to not return a Promise.

Client example in examples/promise_batches/client.js showing how to properly execute a batch request:

const jayson = require('jayson/promise');

const client = jayson.Client.http({
  port: 3000
});

const batch = [
  client.request('add', [1, 2, 3, 4, 5], undefined, false),
  client.request('add', [5, 6, 7, 8, 9], undefined, false),
];

client.request(batch).then(function(responses) {
  console.log(responses[0].result); // 15
  console.log(responses[1].result); // 35
});
Notes
  • The third parameter to PromiseClient.prototype.request above is explicitly set to undefined - this parameter would normally represent the desired ID of the call. Remember that null would mean a notification (which does not return a response) and other falsy values may actually be used as ids. Setting undefined ensures that the id is generated automatically.

Promise Browser Client

A browser client that has no dependencies on node.js core libraries is available too. It works similar to how the regular callback-style Browser Client works. Here is an example:

'use strict';

const jaysonPromiseBrowserClient = require('jayson/promise/lib/client/browser');
const fetch = require('node-fetch');

const callServer = function(request) {
  const options = {
    method: 'POST',
    body: request,
    headers: {
      'Content-Type': 'application/json',
    }
  };
  return fetch('http://localhost:3000', options).then(res => res.text());
};

const client = new jaysonPromiseBrowserClient(callServer, {
  // other options go here
});

client.request('multiply', [5, 5]).then(function(result) {
  console.log(result);
}, function(err) {
  console.error(err);
});

Please refer to the regular browser client section of the README for more information.

FAQ

How can I pass HTTP headers/session/etc into my JSON-RPC request handler?

Support for method context added in version 3.0.0

See Server context section.

What is the recommended way to use jayson?

Using 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):

These are not issues with jayson, but stem from the fact that JSON-RPC 2.0 specification 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 and compatibility with other non-jayson servers or clients when it comes to the application layer is pretty much guaranteed.

The library author 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.

Example of a http server built with express in examples/faq_recommended_http_server/server.js:

const jayson = require('jayson');
const jsonParser = require('body-parser').json;
const express = require('express');
const app = express();

// create a plain jayson server
const server = new jayson.Server({
  add: function(numbers, callback) {
    const sum = Object.keys(numbers).reduce(function(sum, key) { return sum + numbers[key]; }, 0);
    callback(null, sum);
  }
});

app.use(jsonParser()); // <- here we can deal with maximum body sizes, etc
app.use(function(req, res, next) {
  const request = req.body;
  // <- here we can check headers, modify the request, do logging, etc
  server.call(request, function(err, response) {
    if(err) {
      // if err is an Error, err is NOT a json-rpc error
      if(err instanceof Error) return next(err);
      // <- deal with json-rpc errors here, typically caused by the user
      res.status(400);
      res.send(err);
      return;
    }
    // <- here we can mutate the response, set response headers, etc
    if(response) {
      res.send(response);
    } else {
      // empty response (could be a notification)
      res.status(204);
      res.send('');
    }
  });
});

app.listen(3001);

Using 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:

const jayson = require('jayson');
const request = require('superagent');

// generate a json-rpc version 2 compatible request (non-notification)
const requestBody = jayson.Utils.request('add', [1,2,3,4], undefined, {
  version: 2, // generate a version 2 request
});

request.post('http://localhost:3001')
  // <- here we can setup timeouts, set headers, cookies, etc
  .timeout({response: 5000, deadline: 60000})
  .send(requestBody)
  .end(function(err, response) {
    if(err) {
      // superagent considers 300-499 status codes to be errors
      // @see http://visionmedia.github.io/superagent/#error-handling
      if(!err.status) throw err;
      const body = err.response.body;
      // body may be a JSON-RPC error, or something completely different
      // it can be handled here
      if(body && body.error && jayson.Utils.Response.isValidError(body.error, 2)) {
        // the error body was a valid JSON-RPC version 2
        // we may wish to deal with it differently
        console.err(body.error);
        return;
      }
      throw err; // error was something completely different
    }

    const body = response.body;

    // check if we got a valid JSON-RPC 2.0 response
    if(!jayson.Utils.Response.isValidResponse(body, 2)) {
      console.err(body);
    }

    if(body.error) {
      // we have a json-rpc error...
      console.err(body.error); // 10!
    } else {
      // do something useful with the result
      console.log(body.result); // 10!
    }
  });

Contributing

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

Please make sure to follow the style of the project, and lint your code with npm run lint before submitting a patch.

Submitting issues or pull requests with the Typescript type definitions

You 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. Better yet, issue a pull request adding a test there yourself that shows up when running the package.json script test-tsc.

jayson's People

Contributors

bdunlay avatar dependabot[bot] avatar derrell avatar dvicory avatar franck34 avatar gillesdemey avatar isaacgr avatar jaymes-bearden avatar joelgallant avatar m-schmoock avatar net147 avatar rmg avatar shimks avatar switchtrue avatar tedeh avatar willvincent avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

jayson's Issues

Dynamic methods

Hey,

Have you thought of allowing dynamic methods on the server? Methods that are not predefined as they are now but rather resolved dynamically by the user's code? That would make it easier to delegate to preexisting objects and handle larger API more conveniently.

Something like:

function handler(name) {
  obj[name].apply(obj, Array.prototype.slice.call(arguments, 1))
}
handler.has = function(name) { return typeof obj[name] == "function" }

Jayson.server(handler)

Callback with Errors Example

I've looked through the tests, the examples, and the documentation, and there's nothing specified about how to use the error argument of the callback.

Through trial and error I've narrowed it down to this:

callback({ code: CODE, message: MESSAGE });

Where CODE is a numerical code like -32602 and MESSAGE is an arbitrary string. Omitting MESSAGE causes a { code: -32603, message: 'Internal error' } to be returned instead.

Adding a simple example for handling errors would go a long ways towards resolving this ambiguity. All the calls I found were callback(null, ...).

Change request timeout

According to this: http://nodejs.org/api/http.html#http_request_settimeout_timeout_callback
http sockets are automatically closed after 120 seconds of inactivity by default, how is it possible to send an http RPC request throught jayson with any desired timeout (more than 120 seconds of course)

I used:

this.rpcClient = jayson.client.http({
port: port,
hostname: ip
});

this.rpcClient.on('http request', function(req){
req.setTimeout(VALUE) ;
});

but did not work for me

Change forcing POST to optional GET

Hi,

i have problem with this line:

/node_modules/jayson/lib/server/middleware.js

//  405 method not allowed if not POST
if(!utils.isMethod(req, 'POST')) return error(405, { 'allow': 'POST' });

Can u make small new version, with something like this ?

if (options.allowGet) {
    if(!utils.isMethod(req, 'POST') && !utils.isMethod(req, 'GET')) return error(405, { 'allow': 'POST or GET' });
} else {
    if(!utils.isMethod(req, 'POST')) return error(405, { 'allow': 'POST' });
}

Set a Cookie

If I want to set a cookie with a req/res, is the best option to use the middleware approach with Express/Connect? I tried out using Express, but the cookie was only written if my middleware module came before the Jayson middleware. Unfortunately, the data I need to write into the cookie is retrieved in the method of the request. Any way to access the headers on the raw response?

Unable to make client requests for Remote HTTP resource

.
.
var jsonrpc = require('jayson');
var rpc = jsonrpc.client.http({
    host: 'http://www.mywebsite.com/api/',
    port: 80
});

rpc.request('ping', [], function(err, response) {
    if (err)
        console.log("RPC Error: ", err);
    // request was received successfully
    console.log("From RPC: ", response);
});
.
.

Above request was throwing following error,

RPC Error:  { [Error: getaddrinfo EADDRINFO] code: 'EADDRINFO', errno: 'EADDRINFO', syscall: 'getaddrinfo' }

TypeError with collect: false and no params in request

When using collect: false to not collect parameters into a single argument, if no parameters are supplied the error TypeError: Cannot read property 'length' of undefined is raised due to a length check on params which is null.

The JSON RPC 2.0 spec (here) says that params may be omitted.

/Users/mike/git/project/node_modules/jayson/lib/method.js:130
  if(handler.length !== (params.length + 1)) {
                               ^

TypeError: Cannot read property 'length' of undefined
    at Method.execute (/Users/mike/git/project/node_modules/jayson/lib/method.js:130:32)
    at /Users/mike/git/project/node_modules/jayson/lib/server/index.js:294:12
    at maybeParse (/Users/mike/git/project/node_modules/jayson/lib/server/index.js:411:5)
    at Server.call (/Users/mike/git/project/node_modules/jayson/lib/server/index.js:237:3)
    at /Users/mike/git/project/node_modules/jayson/lib/utils.js:189:14
    at /Users/mike/git/project/node_modules/jayson/node_modules/lodash/index.js:7173:25
    at /Users/mike/git/project/node_modules/jayson/lib/utils.js:162:7
    at Object.Utils.JSON.parse (/Users/mike/git/project/node_modules/jayson/lib/utils.js:284:3)
    at IncomingMessage.<anonymous> (/Users/mike/git/project/node_modules/jayson/lib/utils.js:160:16)
    at emitNone (events.js:80:13)

exception callback

Hey!

I'm using the promise-based interface and I'm having some troubles logging exceptions my methods throw. Currently I have to .then my own promise in every method in order to log a potential exception. What do you think of a callback property on the server object that gets called whenever a method "throws" an exception?

Example of CLI

Can you provide an example of how to use the CLI? I just keep getting errors with regard to how to pass parameters.

Release TLS support

The latest published version 1.2.0 does not contain the TLS support added in #47. Can a new version be published?

Websocket support

Hi there, Is it possible for you to explain how to add a websocket based json rpc v2 layer.

Thanks in advance.

Access to request/response headers

I am using Jayson to authenticate with a JSON-RPC service, where I need to get the first server responses' set-cookie header and set it in subsequent requests headers. Something like this:

client.request('Tinebase.login', [username, password], function(err, error, result) {
  if(err) throw err;
  console.log(result);
  client.options.headers = client.options.headers || {}; 
  client.options.headers[ 'X-Tine20-JsonKey' ] = result['jsonKey']; // Set http.client.headers for subsequent requests
  client.options.headers[ 'cookie' ] = result.headers[ 'set-cookie' ]; // access headers of the reponse object
});

for this to work I patched lib/client/http.js. I wanted to ask you if you can provide headers or apply the patch in Jayson source?

changelog

Hi, could you update the changelog to the latest version?

Callback syntactic sugar with 3 args

How I can use "Callback syntactic sugar" with 3 arguments? I'd tested it by

callback( null, {'code': 100, 'message': "Some error"}, null );

but it does not work right, it returns error object in result field.
Thanks)

[Question] Can the transport server be the RPC client?

Hi there. I need a JSON-RPC connection where the TCP server is the RPC client and vice versa. Basically, this means I want to create a JSON-RPC server around a WebSocket object in the browser. Can I do this with jayson or any other JSON-RPC library you know of? Right now the only way I'm finding to do this would be to manually invoke the middleware function, but I'm not sure how to prepare the request and response objects to do that, and there's no point figuring out how to make that work if there's an easier way.

Thanks. :)

Tcp server does not respond after the first call

Use the tcp connection is a good way to have a two-way socket, with which you can go about taking an open connection.
After the fix to issue #41 was inserted "_.once()" on this line.

Is there a particular reason for this?
Removing "_.once()" the server responds to subsequent calls while keeping the same connection.

I noticed that the event is handled "error" of tcp socket. If this is not done the error arrives at global "uncaughtException" of process.
I added this to fix the problem:

function getTcpListener(self, server) {
  return function(conn) {
    var options = self.options || {};

    conn.on('error', function() {
      conn.end();
    });
...

I opened an issue because I don't know if these are appropriate fix.
Great module, saved me a lot of time!

Headers info not required

"POST / HTTP/1.1 Content-Length: 90 Content-Type: application/json Accept: application/json Host: 127.0.0.1:9040 Connection: keep-alive {"method":"hello","jsonrpc":"2.0","params":[],"id":"2cf4c22b-453d-4b0a-92ee-6566ede19403"}"

POST / HTTP/1.1 Content-Length: 90 Content-Type: application/json Accept: application/json Host: 127.0.0.1:9040 Connection: keep-alive

I don't want bold part.

I don't know if I'm not able to find or it can't be changed.

Please let me know how to fix this behavior.

-Yash

TCP Transport Support

JSON-RPC supports TCP transport as of 2.0 so it would be good if it is supported in this library as well.

is this a mistake

\jayson\lib\client\http.js line : 38
utils.JSON.stringify(request, options, function(err, body) {
if(err) return callback(null, str);

what is the second param (str) of callback used for?

Use Jayson as handler function in http server code

Check out this link

I'm able to use a handler inside a createServer function, which means that non-POST requests can be
handled cleanly along file serving handlers like Lactate (look at the section titled 'Creating a directory handler')

Is there any way of doing the same with this library?

null Error and id validation

In the client library, when jayson got error: null response it assumes request returned an error because it only checks if typeof(res.error) !== 'undefined'. Another thing is I don't see in the code any id of response validation, etc.

Enable authentication

As per specifications jsonrpcx, it would be interesting to add an example of how to implement an authentication system.

It would be very welcome also the possibility to manage the methods as middleware, in order to separate the layer of validation from the rest of the method.

If the server crashes during response handling request still succeeds

Hey,

I noticed that when the server crashes in the response event handler, the client's callback is still called, just without the response argument. But because notification requests also call the callback without the response argument, it's kind of hard to distinguish an error condition from the normal case. Not to mention there should be an error given to the callback when this happens.

dictionary as top level parameter

For now, it is possible to transfer parameters in an Array. The dictionary should be the first object in this array.

client.request('somemethod', [{"param1","param1_value"}], function(err, error, response) {
    if(err) throw err;
});

Is it possible to make the following feasible?

client.request('somemethod', {"param1","param1_value"}, function(err, error, response) {
    if(err) throw err;
});

Tcp server did not respond 'Parse Error'

Hi,
When I wrote to the tcp server with some string not a JSON format, it didn't respond me an 'Parse Error', but the server crashed with 'Error: Unexpected "a" at position 0 in state START'.
The error was thrown from jsonparse module, but unhandled in Utils.parseStream
image
I fix this with the fellowing code, more details in the comment.

Utils.parseStream = function(stream, options, onRequest) {

  var result = JSONStream.parse();

  stream.pipe(result);

  result.on('data', function(data) {

    // apply reviver walk function to prevent stringify/parse again
    if(typeof(options.reviver) === 'function') {
      data = Utils.walk({'': data}, '', options.reviver);
    }

    onRequest(null, data);
  });

  // the jsonparse module always parse until buffer end,
  // because the thrown error has passed to JSONStream stream emit,
  // it never stop while parsing error.
  // e.g. 
  // if buffer is 'abc', it will emit error three times:
  // Unexpected "a" at position 0 in state START
  // Unexpected "b" at position 1 in state START
  // Unexpected "c" at position 2 in state START
  // 
  // add a errored flag to prevent onRequest error from calling more than once
  var errored = false;
  result.on('error', function(err) {
    if (!errored) {
      onRequest(err);
      errored = true;
    }
  });

};

I'm not sure it is graceful, so I just submit a issue not pr. Hope you fix it in a better way.

Unable to use https with SSL certs

var credentials = {
    key: fs.readFileSync('./certs/key.pem'),
    cert: fs.readFileSync('./certs/cert.crt'),
    ca: fs.readFileSync('./certs/intermediate.crt')
};

server.https(credentials).listen(8000);

That doesn't work. Please update your documentation to describe the proper way to support HTTPS

Argument overflow chasing off callback

I'm wondering how you protect against a situation where an RPC supplies more arguments than the target function's signature defines...

e.g

function TargetFunc(arg1, arg2, arg3, callback)
{ "jsonrpc": "2.0", "method": "TargetFunc", "params": [ "arg1", "arg2", "arg3", "arg4" ], "id":1 }

In the above example, callback will be populated with "arg4" and the real callback object will not be accessible.
This means it's difficult to report "too many arguments were supplied" (or indeed anything at all) to the RPC consumer.

Thanks

Support for client https

Hello

I wonder if you could make https protocol supported, it doesn't seem to be supported.

Given this simple test.js which tries to connect to a https JSON server:

'use strict';
var jayson = require('jayson');
var client = jayson.client.http('https://mywebsite.com/api/');

client.request('test1', ['value1', 'value2'], function (error, value) {
  if(error) {
      console.log(error);
  }
  else {
      console.log(value);
  }
});

I get this response:

http.js:1821
    throw new Error('Protocol:' + options.protocol + ' not supported.');
          ^
Error: Protocol:https: not supported.
    at Object.exports.request (http.js:1821:11)
    at /Users/angel/Documents/cloud_manager/server/node_modules/jayson/lib/client/http.js:50:20
    at Object.Utils.JSON.stringify (/Users/angel/Documents/cloud_manager/server/node_modules/jayson/lib/utils.js:340:3)
    at HttpClient._request (/Users/angel/Documents/cloud_manager/server/node_modules/jayson/lib/client/http.js:37:14)
    at Client.request (/Users/angel/Documents/cloud_manager/server/node_modules/jayson/lib/client.js:107:8)
    at Object.<anonymous> (/Users/angel/Documents/cloud_manager/server/test.js:8:8)
    at Module._compile (module.js:456:26)
    at Object.Module._extensions..js (module.js:474:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:312:12)

node v0.10.15
jayson v1.0.10

I am new to NodeJS and I cannot work out how to enable it in your code. Thanks!

Named parameters should be removed

Hey,

While named parameters might be a good feature for some people, they should not be the responsibility of a JSON-RPC library. Named parameters are a different concern and don't belong to the session layer.

Specify charset in Content-Type

The jayson server returns Content-Type header application/json with utf-8 body
Some http clients default to ISO_8859_1 as default charset which cause issues

The specs seem to be a bit unclear on this:
http://stackoverflow.com/questions/2325571/for-http-responses-with-content-types-suggesting-character-data-which-charset-s

As far as I know the JSON-RPC spec has no specific charset defined.

In any case I think it would not be bad to explicitly set Content-Type to application/json; charset=utf-8 (or the encoding defined in the options)

Set headers to send sessions'cookies

Hi,

I can't find a way to writeHead and send a cookie to the client. I've tryied by using connect() and use a security module before using the jayson module, but when I call res.writeHead('myheaders') and call next() to let jayson do its job, I get this exception :

_http_outgoing.js:331
    throw new Error('Can\'t set headers after they are sent.');

Named function with named parameters in request

If I use named function when create a server, e.g.:

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

it does not work, b/c method Utils.getParameterNames in lib/utils.js has incorrect RegExp for this case. May be you change this regex to something like this /^function [\w\d]+\((.+?)\)/. Thanks.

\" kills the parser

Hey,

Having \" show up in the request JSON kills Utils.parseBody which seems to be a hand-rolled JSON parser. That in turn hangs the connection.

Thanks for sharing your code, but seriously, dude, don't reinvent wheels. And if you do, make sure you have tests for it. ;-)

32600 - Invalid request

Hello,

First, thanks for your wonderfull work.

I'm trying to have a both communication point from Golang (client - serv) and Node (with your lib) (client serv)

When I try from my program in Go to call 127.0.0.1 3000 (because I'm running the node server on local). I got the following message ๐Ÿ‘

[{"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":"Invalid request"}},{"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":"Invalid request"}}]

Have you any doc that could allow me to bind a valid request because for now I just send to the server (in json) an array of int like =>

[10, 10]

The server code =>

// create a server
var server = jayson.server({
  add: function(args, callback) {
    callback(null, args[0] + args[1]);
  }
});

server.tcp().listen(3000);

Client code in go =>

var reply int
conn, err := net.Dial("tcp", r.Client[0].Addr)

if err != nil {
	fmt.Println(err)
	return
}

defer conn.Close()

c := jsonrpc.NewClient(conn)
err = c.Call("add", []int{10, 10}, &reply)

if err != nil {
	fmt.Println(err)
	return
}
	
fmt.Printf("Add: %d", reply)

Thank you :)

Request timeout option

Currently the requests will never timeout if the server didn't respond. After getting stream object, call req.timeout:

This is from http.js, line 51:

var req = self._getStream(options);

Just add this after it:

req.setTimeout(10000, function(){callback(new Error('timeout'));});

Add the ability to allow Jayson to support CORS requests.

I have a short patch to lib/utils.js that enables Jayson to support CORS requests.

It allows requestHeaders to be added to responses and supports requests with the HTTP OPTIONS methods.

I am a bit of a GITHUB newbie, so I am open to the best approach to submitting the patch if the project is open to receiving them.

With my changes CORS requests can be enabled by doing the following in server code:

var server = jayson.server({/* json rpc implementation*/});
var allowCors = {"Access-Control-Allow-Origin":"*", "Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept"};

server.http( allowCors ).listen( 3000 );

Thank you,
Rob

Documentation class names inconsistancy

I've noticed an inconsistency between classes name and the documentation for the respective classes:

Class Name Documented As
HttpClient ClientHttp
HttpServer ServerHttp
HttpsClient ClientHttps
HttpsServer ServerHttps
TcpServer ServerTcp

Example: TcpServer & ServerTcp

TcpClient does not follow this convention:

Class Name Documented As
TcpClient TcpClient

I think they should all be like the TcpClient (documentation name matching class name) but would like to run it by you before you take my tls-support PR so I can follow the proper documentation naming convention.

Ability to pass debug information in a response

I want to be able to access debug information about a response. Currently whatever I return from the handler must be what will be the reply. To work around it I must make my results into objects and attach an info property, and then in the server.call callback I must filter this from being sent.

server.call(message, (error, success, info) => {

}

Maybe adding an info field to the callback as above would resolve this?

I think the more generic concept to enable this functionality would be to allow middleware in the request/response flow like Express/Koa.

es6 class as handler

Hey,

I tried to pass es6 class instead of function - I've got
TypeError: Class constructor GetMethod cannot be invoked without 'new'

It happened because Method.prototype.execute expects that a handler is a function (it just calls it and replaces a context). I've checked why it does not work properly for my code and I found this:
https://github.com/tedeh/jayson/blob/master/lib/method.js#L120

First problem for me is that it overrides my context - if I use for handling a class which inherits prototype from any abstract class, it also overrides this prototype.

For example, I have the handler defined this way:

var AbstractMethod = function () {
    this.randomprop = 'kex';
}
var AbstractMethod = require('./AbstractMethod.js');

var GetMethod = function (args, callback) {
    AbstractMethod.call(this);
    console.log('GetMethod', this);
    // prints "GetMethod { jayson server props }"
    this.test(callback);
    //TypeError: this.test is not a function (because prototype was replaced by 'call' in #120)
};

GetMethod.prototype = Object.create(AbstractMethod.prototype);

GetMethod.prototype.test = function (callback) {
    console.log('test/callback')
    callback(null, 'ok')
}

Changing lib/method.js#120 from
return handler.call(server, params, callback);
to
return new handler(server, params, callback);

solves my problem, but it's not good solution for people who currently use it. server object is available in the constructor (I can use it if I want and drop if I don't need it), my this context is not affected and it creates an instance when class is defined with class operator.

What do you think about new options (for es6 support with backward compatibility), for example:

  • es6_classes_support - enables possbility to pass es6 class as handler
  • rewrite_context - rewrites context of handler (true as default, because it currently works this way)

Of course, I can make a PR, but I want to know what do you think about it. It's ~20 lines of code.

Best Regards

Passing a request without params can cause the server to crash.

Let's say this is my rpc server.

var jayson = require('jayson');

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

server.http().listen(3000);

If I were to have a client post this:

{"jsonrpc":"2.0","method":"add","params":[1, 2]}

That would work correctly and I would get back 3.

But if I make a simple mistake like this:

{"jsonrpc":"2.0","method":"add","params":[]}

This will always throw a fatal error and crash the server.

What I would expect to be correct behavior is that a and b should become set to undefined because they weren't passed as params. That would be okay... worst case scenario you get a nonsensical response back like null because that's the result of undefined + undefined. But what actually happens is: b and callback become undefined and a becomes the callback function.

So when the callback is called you get a Fatal error message telling you undefined is not a function, and the server crashes.

There is an absolutely ridiculous way to get around it, and at least keep the server running.

var jayson = require('jayson');

var server = jayson.server({
    add: function(a, b, callback) {
      if (typeof a === 'function') {
        callback = a;
        a = undefined;
      }

      if (typeof b === 'function') {
        callback = b;
        b = undefined;
      }

      callback(null, a+b);
    }
});

server.http().listen(3000);

I've looked through your module a little bit. I've also looked at other json-rpc modules on npm and I have to say, code clarity wise yours is by far the best. So ๐Ÿ‘ to you!

I would like to make a fork and do a pull request to address this issue. Do you see this as an actual issue? Or is this the intended behavior. As long as I'm running my server as a daemon which restarts when it fails, I'm not really too concerned about it. But yeah I kind of see it as a bad thing to have a simple typo or mistake in the request payload cause a server crash.

Access to the client information

Can I access to the client information when server function is called (e.g. client IP-address)?
Thanks.

var server = jayson.server({
  some: function(callback) {
    // client info need here
    callback();
  }
});

Handling wrong number of arguments

The server chokes when it doesn't get the right number of arguments. For example, say you have a very simple server that defines an add method:

var jayson = require("jayson");

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

server.http().listen(3000);

Using the CLI we can send it a request with the wrong number of parameters:

jayson -u http://localhost:3000 -m add -p [1]

Th result is that the server throws an unhandled exception, and then eventually the client dies because of a closed TCP connection.

$ jayson -u http://localhost:3000 -m add -p [1]
-> add(1)
<- Error: socket hang up
    at createHangUpError (http.js:1472:15)
    at Socket.socketOnEnd [as onend] (http.js:1568:23)
    at Socket.g (events.js:180:16)
    at Socket.EventEmitter.emit (events.js:117:20)
    at _stream_readable.js:920:16
    at process._tickCallback (node.js:415:13)
$ node simpleserver.js 
.../simpleserver.js:6
    callback(null, a + b);
    ^
TypeError: undefined is not a function
    at jayson.server.add (.../simpleserver.js:6:5)
    at Method.execute (.../node_modules/jayson/lib/method.js:124:20)
    at .../node_modules/jayson/lib/server.js:284:12
    at maybeParse (.../node_modules/jayson/lib/server.js:401:5)
    at Server.call (.../node_modules/jayson/lib/server.js:229:3)
    at .../node_modules/jayson/lib/utils.js:180:14
    at .../node_modules/jayson/node_modules/lodash/index.js:7173:25
    at .../node_modules/jayson/lib/utils.js:153:7
    at Object.Utils.JSON.parse (.../node_modules/jayson/lib/utils.js:273:3)
    at IncomingMessage.<anonymous> (.../node_modules/jayson/lib/utils.js:151:16)

Shouldn't it handle this and return an error to the client?

Example of middleware server

I'm trying to use Jayson with Express library, but there is no any example of it. Can you add it to examples directory?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.