JSON RPC 2.0

编辑于2018年08月14日

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
});

以上就是 jayson 的一些基础用法,demo 可以从 GitHub 上克隆下来运行,更丰富的功能见文档说明