前言
最近刚加入区块链学习的热潮,从一些基本技术开始学起。本文翻译自JSON-RPC 2.0 Specification. 其实协议很简单,本不需要翻译,主要是为了记录这个学习过程,以及加深理解。
1. 概览
JSON是一种轻量级的数据交换格式。它可以地标数字,字符串,有序数组,以及键值对。
而JSON-RPC是一种无状态的,轻量级的远程程序调用协议。不只一种数据结构及其处理规则可以符合这种定义。数据通过socket, http, 或其他环境传输,并不能确定其将在本进程内使用。它使用JSON来座位数据格式。
设计也很简单。
2. 约定
关键词“MUST”, "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", "OPTIONAL"的解释可以在RFC2119中找到。
由于采用了JSON协议,其可传输的数据类型也和JSON相同。JSON可以表示四种基本类型, Strings, Numbers, Booleans, Null,还有两种结构类型:Objects, Arrays。当我们提到“基本类型”的时候,是指四种基本的JSON类型,而“结构类型”则指代以上两种JSON结构类型。当提到JSON类型时,总会把第一个字母大写,比如Object, Array, String, Number, Boolean, Null. True和False也是如此。
3. 兼容
JSON-RPC 2.0定义的请求对象和响应对象和现有的JSON-RPC 1.0客户端/服务器有兼容问题。这两个版本其实很好区分,2.0定义了一个叫"jsonrpc"的成员,其值时2.0,而1.0版本没有。2.0的实现往往要兼容1.0的对象,即使我们在开发点对点以外或者明显不是1.0的业务的时候亦是如此。
4. 请求对象
RPC调用是指发送一个请求对象到远程服务器上。请求对象包括以下成员:
jsonrpc: 用来声明JSON-RPC协议的版本,固定为“2.0”
method:需要调用的方法。方法名以单词rpc开头,后面跟上期限字符,这个字段在rpc交互的方法及其扩展上被存储起来,并且不能用于其他意义。
params: 结构化的值,用于存储方法响应需要用到的参数,且不能被省略。
id:客户端分配的一个标识符,可以包含字符串,数字或者为空。如果没有id,就会被当成是广播通知。这个值一般不能为Null,且为数字时不能有小数。如果请求中含有这个字段,服务器在响应时,必须原样返回该字段,用来关联以下两个对象的不同环境:
- 请求对象中不推荐使用Null作为id的值,因为2.0声明中使用Null来响应未知的id。而JSON-RPC 1.0版本则使用Null的id来作为通知,这会引起混淆。
- 数字的小数部分也会有问题,因为十进制的小数不能用二进制小数来精确表示。
4.1 通知
当请求参数中不发送id成员时,该请求会被当作通知处理。对于通知,客户端不关心响应对象,因为服务端也没必要返回响应对象,确切的说,服务端不准答复一个通知请求,即便这个请求是批处理请求中的一个。
根据这个定义,通知请求并不会被确认,因为客户端不会收到响应对象,更进一步说,通知请求的客户端无法感知到错误,比如参数错误/网络错误等。
4.2 参数结构
RPC调用的参数必须是结构化的值(对象或者数组),对象通过名字遍历,而数组通过位置可遍历。
数组参数的遍历顺序必须与服务端顺序一致。
对象参数的成员值必须与服务端期望的一致,且在大小写上精确匹配。一旦有成员缺失,会导致错误产生。
- 响应对象
除了通知,服务器上收到RPC调用时,必须做出响应。响应对象时一个JSON对象,包含以下成员:
jsonrpc: JSON-RPC协议版本,指定"2.0"
result: 成功响应时包含该字段,有错误发生则不包含。其取值取决于服务器上定义的方法的响应。
error: 当有错误发生时含该字段,正确处理时不能含有该字段。其具体值定义在5.1中。
id: 这个字段必须包含,且取值和请求对象中的id字段相同。如若检测解析请求对象中的id出错,则使用Null。
不管响应中含有result还是error,不能同时含有result和error。
5.1 Error对象
当RPC请求出错时,服务器响应的Response对象中,必须包含error,且包含以下成员:
code: Number类型,表示出错类型
message: String类型 简介的一句话来描述错误
data: 可以是基本类型,也可以是结构类型,来表示错误的额外信息,且可以缺省。具体取值由服务器自定义,比如错误详情,访问限制等等。
-32768到-32000之间的错误码是系统保留错误码,协议预定义方便未来使用。错误码的定义和XML-RPC类似:
-32700: 解析错误,无效的JSON结构,服务器在解析JSON时出错
-32600: 请求无效,Request对象不是一个合法的JSON请求
-32601: 未知的方法,服务器未定义该method,或者该方法不可用
-32602: 参数错误
-32603: 网络错误
-32000--32099: 服务器错误,服务器其他错误的保留错误码
上述区间以外的错误码可在应用开发时使用。
6. 批处理
同时发送多个Request对象时, 客户端可以把请求都放到一个数组里一起发送。
服务端收到Request对象数组并处理完成后,应当以数组的形式返回,且数组中包含了响应的请求的Response对象。每一个请求对应一个响应,如果请求是通知的话,则不包含该Response对象。服务端在批处理请求任务时,可以按任何顺序或者并行化处理。
服务端对请求进行批处理时者不是至少长度为1的合法请求对象数组时,服务器响应的对象必须是一个单的Response对象。如果没有Response数组中不包含Response对象,那也不能返回空数组,而应该什么都不返回。
7. 举例
7.1. 使用数组参数
--> {"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": 1}
<-- {"jsonrpc": "2.0", "result": 19, "id": 1}
--> {"jsonrpc": "2.0", "method": "subtract", "params": [23, 42], "id": 2}<-- {"jsonrpc": "2.0", "result": -19, "id": 2}
7.2. 使用对象参数
--> {"jsonrpc": "2.0", "method": "subtract", "params": {"subtrahend": 23, "minuend": 42}, "id": 3}<-- {"jsonrpc": "2.0", "result": 19, "id": 3}--> {"jsonrpc": "2.0", "method": "subtract", "params": {"minuend": 42, "subtrahend": 23}, "id": 4}<-- {"jsonrpc": "2.0", "result": 19, "id": 4}
7.3. 通知
--> {"jsonrpc": "2.0", "method": "update", "params": [1,2,3,4,5]}
--> {"jsonrpc": "2.0", "method": "foobar"}
7.4. 方法不存在
--> {"jsonrpc": "2.0", "method": "foobar", "id": "1"}
<-- {"jsonrpc": "2.0", "error": {"code": -32601, "message": "Method not found"}, "id": "1"}
7.5. Request对象不是合法的JSON
--> {"jsonrpc": "2.0", "method": "foobar, "params": "bar", "baz]
<-- {"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error"}, "id": null}
7.6. Request请求对象无效(相关字段类型不符合文档定义)
--> {"jsonrpc": "2.0", "method": 1, "params": "bar"}
<-- {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null}
7.7. Request对象为数组,但不是合法JSON
--> [ {"jsonrpc": "2.0", "method": "sum", "params": [1,2,4], "id": "1"}, {"jsonrpc": "2.0", "method"]<-- {"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error"}, "id": null}
7.8. Request对象为空数组
--> []<-- {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null}
7.9. Request对象为数组,但数组成员不是合法的Request对象:
--> [1]
<-- [ {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null}]
7.10. Request对象为数组,且有多个成员,但部分成员不是合法的Request对象:
--> [1,2,3]
<-- [ {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null}, {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null}, {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null}]
7.11. 正确的批处理调用和响应
--> [
{"jsonrpc": "2.0", "method": "sum", "params": [1,2,4], "id": "1"},
{"jsonrpc": "2.0", "method": "notify_hello", "params": [7]},
{"jsonrpc": "2.0", "method": "subtract", "params": [42,23], "id": "2"},
{"foo": "boo"},
{"jsonrpc": "2.0", "method": "foo.get", "params": {"name": "myself"}, "id": "5"},
{"jsonrpc": "2.0", "method": "get_data", "id": "9"}
]
<-- [
{"jsonrpc": "2.0", "result": 7, "id": "1"},
{"jsonrpc": "2.0", "result": 19, "id": "2"},
{"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null},
{"jsonrpc": "2.0", "error": {"code": -32601, "message": "Method not found"}, "id": "5"},
{"jsonrpc": "2.0", "result": ["hello", 5], "id": "9"}
]
7.12. 全通知类型的批处理调用
--> [
{"jsonrpc": "2.0", "method": "notify_sum", "params": [1,2,4]},
{"jsonrpc": "2.0", "method": "notify_hello", "params": [7]}
]
<-- //Nothing is returned for all notification batches
8. 扩展
使用rpc开头的方法名是系统扩展方法,且不能用于其他场合。每一个系统扩展都在相关声明中定义。系统扩展是可选的。
9. 我们学到什么
1)如何撰写一个规范,或者说一个规范由哪些部分组成,本规范是一个很好的模版
2)如何做前后兼容。jsonrpc的兼容方式很简单,在请求头部扩展一个jsonrpc的版本号即可。如果是良好的设计,在1.0的时候就应该加上此字段。
3)严谨性。比如,我们不能简单的使用Null作为id参数来表示通知,服务端解析id失败也返回Null的id,无法区分这两种情形。
4)批处理。一个好的设计必然要考虑多任务的批处理,在设计批处理时,需要考虑数据的解析,服务器可能不按序处理,以及可能并发处理,需要考虑不同的请求在不同的时许下处理可能产生的影响
5)举例。和测试用例的设计有点类似,尽可能覆盖全面