@produck/jsonrpc
v0.3.1
Published
Communication independent jsonrpc 2.0
Downloads
2
Readme
@produck/jsonrpc
This module is used to help developing rpc application in JSONRPC 2.0 protocol.
Feature
- Only for JSONRPC
2.0
- Client factory with default options
- Good interface to batch request
- Server factory with default options
- Internal timeout for a rpc client
- Warn hook to hanle caught error
- Extensible serializer/deserializer to implement with non-json payload
- Browser-side supported
- Named client-side or server-side
Installing
Using npm:
$ npm install @produck/jsonrpc
Example
The most simple client,
const JsonRpc = require('@produck/jsonrpc');
const client = JsonRpc.Client({
// Providing a function to handle a request payload raw defining how to send.
// Such as send a request by axios...
sendRequest: raw => console.log(raw)
});
// Then do something like below,
// In promise:
client.request('anyMethod')
// handle result
.then(result => console.log(result))
// handle exception
.catch(error => console.error(error));
// Just calling and ignore response
client.notificate('anyMethod', { foo: 'bar' });
// In async function
(async function request() {
try {
// handle result
const result = await client.request('anyMethod');
console.log(result);
} catch (err) {
// handle exception
console.error(err);
}
}());
A most simple server,
const JsonRpc = require('@produck/jsonrpc');
const server = JsonRpc.Server({
// Providing a function to handle a response payload raw defining how to send.
sendRequest: raw => console.log(raw),
methodMap: {
hello() {
return 'hello, world!';
},
add(numberA, numberB) {
return numberA + numberB;
}
}
});
A server and a client comunication. example/client-server-comunication.js
,
const JsonRpc = require('@produck/jsonrpc');
const client = JsonRpc.Client({
sendRequest: raw => server.handleRequest(raw)
});
const server = JsonRpc.Server({
sendResponse: raw => client.handleResponse(raw),
methodMap: {
add: (numA, numB) => numA + numB
}
});
(async function Example() {
const result = await client.request('add', [4, 5]);
console.log(result); // 9
// To destroy the client to avoid memory leaking.
// UNECESSARY to destroy after each `client.request()`;
client.destroy();
}());
A jsonrpc server in http, example/jsonrpc-http-server.js
const JsonRpc = require('@produck/jsonrpc');
const http = require('http');
const server = JsonRpc.Server({
methodMap: {
add: (a, b) => a + b
}
});
function getPayloadData(stream) {
return new Promise((resolve, reject) => {
let data = Buffer.from([]);
stream.on('data', chunk => {
data = Buffer.concat([
data, chunk
], data.length + chunk.length);
}).on('end', () => resolve(data));
});
}
http.createServer(async function JsonRpcRequestListener(req, res) {
const requestBody = await getPayloadData(req);
const responseRaw = await server.handleRequest(requestBody.toString());
res.setHeader('Content-Type', 'application/json');
res.end(responseRaw);
}).listen(8080);
// Use a http client tool like "Postman" to send POST request.
//
// >>>>
// POST http://127.0.0.1:8080
// {"jsonrpc":"2.0","id":2,"method":"add","params":[3,4]}
//
// <<<<
// {"jsonrpc":"2.0","id":2,"result":7}
Client API
A jsonrpc client instance is used to help developer implement request
, notificate
and batch
feature in the specification "Request Object" in JSONRPC 2.0.
Constructor
Creating a jsonrpc client instance. The new
is not necessary.
Client(options?: ClientOptions): Client
All items of Client.Options
are optional.
interface ClientOptions {
/**
* Client peer name.
*/
name?: string,
/**
* A request id generator.
*/
Id?(): number | string;
/**
* How to transport the serialized data.
* @param raw A transport data.
*/
sendRequest?(raw: any): void;
/**
* Coverting a payload to transport format.
* @param payload a valid jsonrpc 2.0 payload objecct
* @default JSON.stringify
*/
serialize?(payload: Payload): any;
/**
* Coverting a raw data from transport format to jsonrpc payload object.
* @param raw A transport data.
* @default JSON.parse
*/
deserialize?(raw: any): Payload;
/**
* Internal @produck/jsonrpc timeout value. It SHOULD be known that `timeout`
* is NOT defined in specification. It use to solve the problem overstocking
* of requests.
*
* The best way to timeout is using `client.handleResponse` to trigger a
* timeout error.
*
* It MUST be greater than `10`.
*
* @default 120000
*/
timeout?: number;
/**
* Handling message from caught error. Default: () => {}
* @param message
*/
warn?(message: any): void;
}
Creating a client,
const JsonRpc = require('@produck/jsonrpc');
// No options
const client = JsonRpc.Client();
// Options with all items
const client2 = JsonRpc.Client({
name: 'foo',
serialize: JSON.stringify,
deserialize: JSON.parse,
sendRequest: raw => console.log(raw),
timeout: 120000,
Id: IdGenerator()
});
function IdGenerator() {
let counter = 0;
return function Id() {
return counter++;
};
}
Client Instance
client.name
To access the name of a client. Default: '<client-anonymous>'
const JsonRpc = require('@produck/jsonrpc');
const client = Client({ name: 'foo' });
console.log(client.name); // >> foo
client.request(method: string, params?: object | any[]): Promise<any>
A rpc call is represented by sending a Request object to a Server. It will create a invoking waiting response from server by client.handleResponse to resolve.
const JsonRpc = require('@produck/jsonrpc');
const client = Client();
(async function ClentRequestExample() {
try {
const result = await clent.request('add', [1, 2]);
} catch (error) {
console.log(error);
}
}());
client.notificate(method: string, params?: object | any[]): void
A Notification is a Request object without an "id" member. A Request object that is a Notification signifies the Client's lack of interest in the corresponding Response object.
const JsonRpc = require('@produck/jsonrpc');
const client = Client();
client.notificate('any'); // Just request without any response.
client.batch(): Client.Batch
To send several Request objects at the same time, the Client MAY send an Array filled with Request objects. See also "Batch"
Creating a batch and how to use batch below,
const JsonRpc = require('@produck/jsonrpc');
const client = JsonRpc.Client();
client.batch()
.request('any', (err, result) => {})
.request('any', [1, 2], (err, result) => {})
.notificate('any', { foo: 'bar' })
.send(); // return a Promise<void> after response incoming.
client.handleResponse(raw): void
A raw data of request payload will be generated when calling client.request()
or batch.send()
. It may get a response raw data contained the result(s) or
error(s) from jsonrpc server.
const JsonRpc = require('@produck/jsonrpc');
const client = JsonRpc.Client({
Id: () => 123,
sendRequest: raw => console.log(raw)
});
(async function Example() {
const result = await client.request('add', [4, 5]);
console.log(result); // 9
// To destroy the client to avoid memory leaking.
// UNECESSARY to destroy after each `client.request()`;
client.destroy();
}());
client.handleResponse('{"jsonrpc":"2.0","id":123,"result":9}');
client.destroy(): void
There is a observer implemented by setInterval() in each "Invoking Registries" of all clients. It is used to find out all timeout invoking to cause "Internal Timeout Error" as soon as possible. So that each client MUST be destroyed if they are not be used any more to avoid memory leaking.
const JsonRpc = require('@produck/jsonrpc');
const client = JsonRpc.Client();
// Do something ...
client.destroy();
And more about "Internal Timeout Error" see also Client Request Timeout.
Batch Instance
Creating from a specifical client instance by client.batch()
to help sending
a serial of request or notification - client.batch()`.
batch.request(method: string, callback: (err, result) => {})
batch.request(method: string, params: object | any[], callback: (err, result) => {})
batch.notificate(mthod: string, params?: object | any[]): void
batch.send(): Promise<void>
Server API
When a rpc call is made, the Server MUST reply with a Response, except for in the case of Notifications.
Contructor
Creating a jsonrpc server instance. The new
is not necessary.
Server(options: ServerOptions): Server
interface ServerOptions {
/**
* Server peer name.
*/
name?: string;
/**
* Coverting a payload to transport format.
* @param payload a valid jsonrpc 2.0 payload objecct
*/
serialize?(payload: Payload): any;
/**
* Coverting a raw data from transport format to jsonrpc payload object.
* @param raw A transport data.
*/
deserialize?(raw: any): Payload;
/**
* How to transport the serialized data.
* @param raw A transport data.
*/
sendResponse?(raw: any): void;
/**
* Handling message from caught error. Default: () => {}
* @param message
*/
warn?(message: any): void;
/**
* A map of registered methods.
*/
methodMap?: MethodMap;
}
Server Instance
server.name
To access the name of a server. Default: '<server-anonymous>'
const JsonRpc = require('@produck/jsonrpc');
const server = Server({ name: 'foo' });
console.log(server.name); // >> foo
server.handleRequest(raw: any): Promise<any>
A raw data of request payload will be incoming as raw
then SHOULD execute
server.handleRequest(raw)
to handle it. Finally, a response raw was generated
to be used by options.sendResponse(raw)
witch has been defined.
const JsonRpc = require('@produck/jsonrpc');
let responseRawFromSendResponse;
const server = JsonRpc.Server({
// for CASE 1
sendResponse: raw => responseRawFromSendResponse = raw,
methodMap: {
add: (numA, numB) => numA + numB
}
});
(async function Example() {
// for CASE 2
const responseRaw = await server
.handleRequst('{"jsonrpc":"2.0","id":1,"method":"add","params":[2,3]}');
console.log(responseRaw === responseRawFromSendResponse);
// >> true
// They are SAME!
}());
There are 2 cases:
- Something must be done in a pair of request-response. (binding a socket)
- All of response can be sent by a same way. (in a http server)
Client request timeout
//todo
Extension - customers payload raw
//todo
JSON-RPC specification
- https://www.jsonrpc.org/