JSON RPC 是一个无状态的、轻量的远程过程调用协议,它基于 JSON 数据格式,允许运行在基于 socket、http 等诸多不同消息传输环境的同一进程中。
本文总结了 JSON RPC 2.0的通信数据格式,以及如何在 Node.js 中使用。
请求对象
发送一个请求对象至服务器端代表一个 RPC 调用,一个请求对象包含以下属性:
- jsonrpc:指定 JSON RPC 协议版本的字符串,必须为“2.0”。
- method:包含要调用方法名称的字符串。以 rpc 开头,用
.
(U+002E or ASCII 46)连接的方法名为预留给 rpc 内部的方法和扩展,不能在其它地方使用。 - params:调用方法所需要的结构化参数值,该参数可以被省略。
- id:由客户端生成的标识符,值只能是字符串、数字或
Null
。如果没有指定该参数,则说明此请求为一个通知。该值不能为Null
或小数。
通知
没有 id 属性的请求为通知,通知请求表明客户端对相应的响应对象并不感兴趣,因此不需要返回响应对象给客户端。服务器不能响应一个通知,包括批量请求中的。
由于通知没有响应对象,所以通知不确定是否被定义。同样,客户端不会察觉到任何错误(例如无效的参数、内部错误)。
参数结构
如果存在参数,则参数只能是数组或对象。如果是数组,则参数的顺序必须与服务器预期的顺序一致。如果是对象,则对象中必须包含服务器预期的参数,缺少参数可能导致出错,名称必须完全匹配,区分大小写。
响应对象
发起一个 rpc 调用时,除通知以外,服务器必须返回响应。响应为一个 JSON 对象,包含以下属性:
- jsonrpc:指定 JSON RPC 协议版本的字符串,必须为“2.0”。
- result:请求成功必须包含该属性,如果请求出错则不能包含该属性。该属性的值取决于服务器中被调用的方法。
- error:请求出错时必须包含该属性,否则不能包含该属性。该属性的值必须是对象,下文中会说明格式。
- id:必须要指定,且必须与请求对象中的 id 相同。若在检查请求对象 id 时出错(例如解析出错或无效的请求),则该值必须
Null
。
响应对象必须要包含 result 或者 error,但不能同时包含两者。
错误对象
当 rpc 调用出错时,响应对象中必须要包含 error 属性,值为包含下列属性的对象:
- code:表明错误类型的数字,必须为整数。
- message:对该错误的简单描述字符串,尽量使用简短的一句话描述。
- data:包含关于错误额外信息的基本数据或结构化数据。该属性可省略,值由服务器决定(例如:详细的错误信息、嵌套错误等)。
-32768 ~ -32000
为保留的预定义错误代码。在该范围内的错误代码不能被明确定义,保留下来以供将来使用。错误代码基本与XML-RPC建议的一样,url:http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php
code | message | meaning |
---|---|---|
-32700 | 解析出错 | 服务端接收到无效的json,该错误发生于服务器尝试解析 json 文本 |
-32600 | 无效的请求 | 发送的 json 不是一个有效的请求对象 |
-32601 | 未找到方法 | 该方法不存在或不可用 |
-32602 | 无效的参数 | 无效的方法参数 |
-32603 | 内部错误 | JSON RPC 内部错误 |
-32000 ~ -32099 | 服务器错误 | 预留用于自定义的服务器错误 |
除此之外剩余的错误类型代码可供应用程序作为自定义错误。
批量调用
当需要同时发送多个请求对象时,客户端可以发送一个包含所有请求对象的数组。
当所有的请求对象处理完成时,服务器需要返回一个包含对应响应对象的数组。每一个请求对象都应该对应一个响应对象,除非是通知的请求对象。服务器可以并发的处理请求,以任意的顺序和任意宽度的并行度。
批量调用的响应对象在数组中的顺序不需要和请求时对应,客户端应该通过各个响应对象中的 id 来匹配对应的请求对象。
如果批量调用的 rpc 本身是一个无效的 JSON 或者只包含一个对象的数组,那么服务器应该响应一个单一的响应对象而不是数组。若批量调用没有需要返回的响应对象,服务器不能返回一个空数组,而应该什么都不返回。
在 Node.js 中使用
通过 Jayson 我们可以快速的创建 JSON RPC 客户端和服务端。Jayson 兼容 JSON RPC 2.0 和 1.0 版本,支持 TCP、HTTP、TLS 等多常用协议。
Jayson 使用起来非常简单,官方 README.md 上有详细的说明。下面简单的介绍一下用法。
我们主要需要用到 jayson.server()
和 jayson.client()
方法,用来创建服务器和客户端实例:
服务器:
// jayson server
const jayson = require('jayson');
const PORT = 8080;
const server = jayson.server({
add: function (args, callback) {
callback(null, args[0] + args[1]);
}
})
server.tcp().listen(PORT, function () {
console.log('Server listen on port', PORT);
});
客户端:
// jayson client
const jayson = require('jayson');
const client = jayson.client.tcp({ port: 8080 });
client.request('add', [1, 1], function (err, res) {
if (err) {
throw err;
}
console.log('call "add" with params: [1, 1], result:', res.result);
})
我们先启动服务器,然后运行客户端代码即可看到控制台打印出相应的结果。
客户端也可以发起一个通知,只用设置 client.request()
的第三个参数为 null
即可:
// server
const server = jayson.server({
ping: function (args, callback) {
console.log(`[${new Date().toLocaleString()}] - Received client ping`)
}
})
// the third parameter is set to "null" to indicate a notification
client.request('ping', null, null, function (err, res) {
if (err) {
throw err;
}
console.log('ping');
})
客户端还可以发起批量请求:
const requests = [
client.request('does_not_exist', [10, 5]),
client.request('add', [1, 1]),
client.request('add', [0, 0], null) // a notification
]
client.request(requests, function (err, res) {
if (err) {
throw err
};
console.log('res', res); // all responses together
});