
JSON-RPC(remote protocol call)是一种以json为协议的远程调用服务。我之所以研究JSON-RPC也是因为发现平时常用的下载工具aria2与常用的aria2 GUI管理工具也是使用JSON-RPC进行数据交互的。所以在这里用NodeJS来编写编写简单的JSON-RPC协议服务端类库给大家参考。
JSON-RPC 相关文档:
- JSON-RPC介绍:https://baike.baidu.com/item/json%20rpc/13675431?fr=aladdin
- JSON-RPC 2.0 规范:https://www.jsonrpc.org/specification
- JSON-RPC 2.0 规范(中文版):http://wiki.geekdream.com/Specification/json-rpc_2.0.html
1.简述JSON-RPC协议
关于JSON-RPC协议的一些文档信息网上很多,我就不详细说明了。
这里就简单说明一下请求、响应与错误异常的情况。
1.1.请求对象
发送一个请求对象至服务端代表一个rpc调用, 一个请求对象包含下列成员:
属性 | 说明 |
---|---|
jsonrpc | 指定JSON-RPC协议版本的字符串,必须准确写为“2.0” |
method | 包含所要调用方法名称的字符串,以rpc开头的方法名,用英文句号(U+002E or ASCII 46)连接的为预留给rpc内部的方法名及扩展名,且不能在其他地方使用 |
params | 调用方法所需要的结构化参数值,该成员参数可以被省略 |
id | 已建立客户端的唯一标识id,值必须包含一个字符串、数值或NULL空值。如果不包含该成员则被认定为是一个通知。该值一般不为NULL[1],若为数值则不应该包含小数[2]。 |
简单理解的话,请求的参数类似于:
{"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": 1}
{"jsonrpc": "2.0", "method": "subtract", "params": {"subtrahend": 23, "minuend": 42}, "id": 3}
1.2.响应对象
当发起一个rpc调用时,除通知之外,服务端都必须回复响应。响应表示为一个JSON对象,使用以下成员:
属性 | 说明 |
---|---|
jsonrpc | 指定JSON-RPC协议版本的字符串,必须准确写为“2.0” |
result | 该成员在成功时必须包含。当调用方法引起错误时必须不包含该成员。服务端中的被调用方法决定了该成员的值。 |
error | 该成员在失败是必须包含。当没有引起错误的时必须不包含该成员。该成员参数值必须为5.1中定义的对象。 |
id | 该成员必须包含。该成员值必须于请求对象中的id成员值一致。若在检查请求对象id时错误(例如参数错误或无效请求),则该值必须为空值。 |
结果类似于:
{"jsonrpc": "2.0", "result": 19, "id": 1}
注:响应对象必须包含result或error成员,但两个成员必须不能同时包含。
1.3.错误异常
当一个rpc调用遇到错误时,返回的响应对象必须包含错误成员参数,并且为带有下列成员参数的对象:
属性 | 说明 |
---|---|
code | 使用数值表示该异常的错误类型。 必须为整数。 |
message | 对该错误的简单描述字符串。 该描述应尽量限定在简短的一句话。 |
data | 包含关于错误附加信息的基本类型或结构化类型。该成员可忽略。 该成员值由服务端定义(例如详细的错误信息,嵌套的错误等)。 |
-32768至-32000为保留的预定义错误代码。在该范围内的错误代码不能被明确定义,保留下列以供将来使用。
code | message | meaning |
---|---|---|
-32700 | Parse error语法解析错误 | 服务端接收到无效的json。该错误发送于服务器尝试解析json文本 |
-32600 | Invalid Request无效请求 | 发送的json不是一个有效的请求对象。 |
-32601 | Method not found找不到方法 | 该方法不存在或无效 |
-32602 | Invalid params无效的参数 | 无效的方法参数。 |
-32603 | Internal error内部错误 | JSON-RPC内部错误。 |
-32000 to -32099 | Server error服务端错误 | 预留用于自定义的服务器错误。 |
除此之外剩余的错误类型代码可供应用程序作为自定义错误。
2.NodeJS的JSON-RPC协议库编写
2.1.完整代码参考(注意看注释):
const JSONRPC = {
VERSION: '2.0',
errorMsg: {},
methods: {}
};
/*
错误信息 参考:https://www.jsonrpc.org/specification#error_object
| code | message | meaning |
| -32700 | Parse error | Invalid JSON was received by the server.An error occurred on the server while parsing the JSON text. |
| -32600 | Invalid Request | The JSON sent is not a valid Request object. |
| -32601 | Method not found | The method does not exist / is not available. |
| -32602 | Invalid params | Invalid method parameter(s). |
| -32603 | Internal error | Internal JSON-RPC error. |
| -32000 to -32099 | Server error | Reserved for implementation-defined server-errors. |
*/
JSONRPC.errorMsg[-32700] = 'Parse Error.';
JSONRPC.errorMsg[-32600] = 'Invalid Request.';
JSONRPC.errorMsg[-32601] = 'Method Not Found.';
JSONRPC.errorMsg[-32602] = 'Invalid Params.';
JSONRPC.errorMsg[-32603] = 'Internal Error.';
/**
* 检查给定的请求是否有效
* JSONRPC调用
* - "jsonrpc" 当前版本== ('2.0')
* - "id" 请求标识,为数字
* - "method" 指的是调用的方法名称的字符串
* @param {Object} rpc
* @return {Boolean}
*/
function validRequest(rpc) {
return rpc.jsonrpc === JSONRPC.VERSION
&& (typeof rpc.id === 'number' || typeof rpc.id === 'string')
&& typeof rpc.method === 'string';
};
/**
* 规范化响应对象
* @param {Object} rpc
* @param {Object} obj
* @return {Object}
*/
function normalize(rpc, obj) {
obj.id = (rpc && typeof rpc.id === 'number' ? rpc.id : null);
obj.jsonrpc = JSONRPC.VERSION;
//如果错误根据错误不存在错误信息的话代码获取错误信息
if (obj.error && !obj.error.message) obj.error.message = JSONRPC.errorMsg[obj.error.code];
return obj;
};
/**
* JSONRPC 请求处理
* @param {Object} rpc
* @param {Function} respond 响应回调
*/
JSONRPC.handleRequest = function (rpc, respond) {
//版本与一些参数验证
if (!validRequest(rpc)) return respond(normalize(rpc, { error: { code: -32600 } }));//请求协议不规范
//函数查找
let method = JSONRPC.methods[rpc.method];
if (typeof method !== 'function') return respond(normalize(rpc, { error: { code: -32601 } }));// 函数或方法未找到
//参数解析
let params = [];
// 未命名参数
if (Array.isArray(rpc.params)) {
params = rpc.params;
} else if (typeof rpc.params === 'object') {
//通过正则表达式过滤出参数名
let names = method.toString().match(/\((.*?)\)/)[1].match(/[\w]+/g);
if (names) {
for (let i of names) params.push(rpc.params[i]);
} else {
//返回错误 不支持参数
return respond(normalize(rpc, { error: { code: -32602, message: 'This service does not support named parameters.' } }));
}
}
// 用给定的错误和结果进行响应
let reply = function (err, result) {
if (err) {
if (typeof err === 'number') {
respond(normalize(rpc, {
error: { code: err }
}));
} else {
respond(normalize(rpc, {//解析异常捕获
error: {
code: err.code || -32603,
message: err.message
}
}));
}
} else {
respond(normalize(rpc, { result: result }));
}
};
// 追加 reply 函数作为最后一个参数
// params.push(reply);
// 调用方法
try {
// reply(false, method.apply(this, params));
reply(false, method.apply(this));
} catch (err) {
reply(err);
}
}
module.exports = JSONRPC;
2.2.代码讲解
首先我们先定义好基本的JSONRPC对象,对象中包含版本信息(VERSION)、错误信息(errorMsg)、与一些函数。
代码中的handleRequest
是核心处理部分。执行逻辑是先对传入的参数数据格式进行校验,然后解析参数中的函数名以及参数,最后执行函数且将执行结果通过回调的方式返回出去。
3.库的使用
这里的话我就以简单的post请求对前后端进行交互。
简单的服务端示例代码:
const http = require('http');
const fs = require('fs');
const url = require('url');
const RPC = require('./jsonrpc');
RPC.methods = {
demo(t) {
return "hello" + (t || "word");
},
asd(a) {
console.log(a);
return 999;
},
aaa(b, c) {
return b + c;
},
eee: (a, v) => {
return a * v;
},
ccc: (o) => {
//异常模拟
return o / asdsad;
},
//几种写法
test(a, b, c, d, e) {
return { a, b, c, d, e };
},
test2:function(a, b, c, d, e) {
return { a, b, c, d, e };
},
test3:(a, b, c, d, e)=>{
return { a, b, c, d, e };
}
};
const jsonrpc = RPC.handleRequest;
const routes = {//<====路由
"/jsonrpc"(req, res) {
var contentType = req.headers['content-type'] || '';
//接收post请求
if (req.method === 'POST' && contentType.indexOf('application/json') >= 0) {
var data = '';
req.setEncoding('utf8');
req.addListener('data', function (chunk) { data += chunk; });
req.addListener('end', function () {
//响应函数
let respond = function (obj) {
var body = JSON.stringify(obj);
res.writeHead(200, {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(body)
});
res.end(body);
};
try {
var rpc = JSON.parse(data),//获取json数据
batch = Array.isArray(rpc);//<===是否批处理
if (batch) {
var responses = [],
len = rpc.length,
pending = len;
for (var i = 0; i < len; ++i) {
(function (rpc) {
jsonrpc(rpc, function (obj) {
responses.push(obj);
if (!--pending) {
respond(responses);
}
});
})(rpc[i]);
}
} else {
jsonrpc(rpc, respond);
}
} catch (err) {
console.log(err);
return respond({ err: 1110 });
}
});
} else {
res.end();
}
},
"/"(request, response) {
response.writeHead(200, { 'Content-Type': 'text/html' });
response.write(`服务已开启`);
response.end();
},
"/404"(request, response) {
response.writeHead(404, { 'Content-Type': 'text/html' });
response.write("404");
response.end();
}
}
// 创建服务器
http.createServer(function (request, response) {
// 解析请求,包括文件名
var pathname = url.parse(request.url).pathname;
// 输出请求的文件名
console.log("Request for " + pathname + " received.");
route = routes[pathname]
if (route) {
route(request, response);
} else {
routes["/404"](request, response);
}
}).listen(8889);
浏览器控制台测试脚本:
fetch('/jsonrpc', {
headers: {
"Content-Type": "application/json"
},
method: 'POST',
body: JSON.stringify([
{ "jsonrpc": "2.0", "method": "test", "id": 1, "params": [1,2,3,4,5] },
{ "jsonrpc": "2.0", "method": "test2", "id": 2, "params": [1,2,3,4,5] },
{ "jsonrpc": "2.0", "method": "test3", "id": 3, "params": [1,2,3,4,5] },
{ "jsonrpc": "2.0", "method": "test", "id": 4, "params": {a:1,b:2,c:3,d:4,e:5} },
{ "jsonrpc": "2.0", "method": "test2", "id": 5, "params": {a:1,b:2,c:3,d:4,e:5} },
{ "jsonrpc": "2.0", "method": "test3", "id": 6, "params": {a:1,b:2,c:3,d:4,e:5} },
])
}).then(v => {
return v.json();
}).then(v => {
console.log(v)
});
4.测试




本文代码放在:https://gitee.com/baojuhua/JS-snippets/tree/master/NodeJS/JOSNRPC
网友评论