Hessian 2.0 序列化协议(中文版)
公司在微服务系统中,序列化协议大多数使用 MessagePack。但是,由于 MessagePack 设计限制,导致微服务接口在增减参数时,只能在最后操作。但是,由于个人操作,难免失误,结果造成因为增减字段导致的事故层出不穷。最近,一些条件成熟,准备推动部门将序列化协议切换到 Hessian。
原以为,切换到 Hessian 就可以万事大吉。但是,在和同事的沟通中发现,同事反馈,Hessian 本身也有一些限制。为了对 Hessian 有一个更深入的了解,干脆就把 Hessian 序列化协议读一遍。看协议,文字不多,干脆就把协议完整翻译一遍。闲言少叙,正文开始。
Hessian 2.0 序列化协议
协议解释
针对该协议有很多言语不详,甚至模糊不清之处,专门做了一些解释和实践,叙述系列文章,用于辅助消化理解。目录如下:
Hessian 2.0 序列化协议(中文版) — Hessian 序列化协议的中文翻译版。根据后面的“协议解释与实战”系列文章,增加了协议内容错误提示。
Hessian 协议解释与实战(一):布尔、日期、浮点数与整数 — 介绍布尔型数据、日期类型、浮点类型数据和整数类型数据等四种类型的数据的处理。
Hessian 协议解释与实战(二):长整型、二进制数据与 Null — 介绍长整数类型数据、二进制数据和
null
等三种类型的数据的处理。Hessian 协议解释与实战(三):字符串 — 专门介绍了关于字符串的处理。由于字符串需要铺垫的基础知识比较多,处理细节也有繁琐,所以单独成篇来介绍。
Hessian 源码分析(Java) — 开始第四篇分析之前,先来介绍一下 Hessian 的源码实现。方便后续展开说明。
Hessian 协议解释与实战(四):数组与集合 — 铺垫了一些关于实例对象的处理,重点介绍关于数组和集合的相关处理。
Hessian 协议解释与实战(五):对象与映射 — 重点介绍关于对象与映射的相关处理。
Hessian、Msgpack 和 JSON 实例对比 — 用实例对比 JSON、Hessian 和 MessagePack 的区别。
Avro、ProtoBuf、Thrift 的模式演进之路 — 翻译的 Martin Kleppmann 的文章,重点对比了 Avro、ProtoBuf、Thrift 的序列化处理思路。
1. 简介
Hessian 是一种设计用于面向对象传输的,动态类型的,二进制序列化 Web 服务协议。
2. 设计目标
Hessian 是动态类型的,紧凑,跨语言的。
Hessian 协议有如下设计目标:
它必须自我描述序列化类型,即不需要外部架构或接口定义。
它必须是语言独立的,同时支持脚本语言。
它必须一次性读取或者写入。
它必须尽可能紧凑。
它必须简单,以便可以有效地测试和实施。
它必须尽可能快。
它必须支持 Unicode 字符串。
它必须支持8位二进制数据而不必转义或使用附件。
它必须支持加密,压缩,签名和事务上下文信封。
3. Hessian 语法
序列化语法
// D瓜哥 · https://www.diguage.com/ · 出品 //
---------------------------------------------------------------------
# 开始
top ::= value
# 8-bit 编码的二进制数据,分割为 64k 的 chunks
binary ::= x41 b1 b0 <binary-data> binary # 非结尾 chunk
::= 'B' b1 b0 <binary-data> # 结尾 chunk (1)
::= [x20-x2f] <binary-data> # 长度为 0-15 的二进制数据
::= [x34-x37] <binary-data> # 长度为 0-1023 的二进制数据
# 布尔 true/false
boolean ::= 'T'
::= 'F'
# 对象定义 (紧凑映射)
class-def ::= 'C' string int string*
# 自 epoch(1970-01-01 00:00:00 UTC)
# 以来的毫秒数,用 64 bit 编码
date ::= x4a b7 b6 b5 b4 b3 b2 b1 b0
# 自 epoch(1970-01-01 00:00:00 UTC)
# 以来的分钟数,用 32 bit 编码
::= x4b b3 b2 b1 b0
# 64-bit IEEE double 双精度浮点类型
double ::= 'D' b7 b6 b5 b4 b3 b2 b1 b0
::= x5b # 0.0
::= x5c # 1.0
::= x5d b0 # byte 转化的 double (-128.0 to 127.0)
::= x5e b1 b0 # short 转化的 double
::= x5f b3 b2 b1 b0 # 32-bit float 单精度浮点数转化的 double
# 32-bit 有符合整数
int ::= 'I' b3 b2 b1 b0
::= [x80-xbf] # -x10 to x3f
::= [xc0-xcf] b0 # -x800 to x7ff
::= [xd0-xd7] b1 b0 # -x40000 to x3ffff
# list/vector
list ::= x55 type value* 'Z' # 可变长度链表,类似 List (2)
::= 'V' type int value* # 固定长度链表,类似 数组
::= x57 value* 'Z' # 可变长度的无类型链表
::= x58 int value* # 固定长度的无类型链表
::= [x70-77] type value* # 固定长度的有类型链表
::= [x78-7f] value* # 固定长度的无类型链表
// TODO 最后一种和倒数第三种有什么区别?
# 64-bit 有符号长整型
long ::= 'L' b7 b6 b5 b4 b3 b2 b1 b0
::= [xd8-xef] # -x08 to x0f
::= [xf0-xff] b0 # -x800 to x7ff
::= [x38-x3f] b1 b0 # -x40000 to x3ffff
::= x59 b3 b2 b1 b0 # 32-bit integer cast to long
# map/object 映射或对象
map ::= 'M' type (value value)* 'Z' # key, value 映射键值对
::= 'H' (value value)* 'Z' # 无类型 key, value 键值对
# null 值
null ::= 'N'
# Object 实例
object ::= 'O' int value*
::= [x60-x6f] value*
# 值引用 (例如循环树或图)
ref ::= x51 int # reference to nth map/list/object
# UTF-8 编码的字符串,分割为 64k 的 chunk
string ::= x52 b1 b0 <utf8-data> string # non-final chunk
::= 'S' b1 b0 <utf8-data> # 长度为 0-65535 的字符串 (3)
::= [x00-x1f] <utf8-data> # 长度为 0-31 的字符串
::= [x30-x34] <utf8-data> # 长度为 0-1023 的字符串 (4)
# 用于面向对象语言的 map/list 类型
type ::= string # 类型名称
::= int # 类型引用
# main production
value ::= null
::= binary
::= boolean
::= class-def value
::= date
::= double
::= int
::= list
::= long
::= map
::= object
::= ref
::= string
---------------------------------------------------------------------
// D瓜哥 · https://www.diguage.com/ · 出品 //
1 | 这里错误!相关解释请看: Hessian 协议解释与实战(二):长整型、二进制数据与 Null:二进制数据。 |
2 | 没有发现这种场景,代码也不可达。相关解释请看: Hessian 协议解释与实战(四):数组与集合。 |
3 | 这里错误!应该是 1024 ~ 32767。代码中也明确写有 length > 0x8000 。相关解释请看: Hessian 协议解释与实战(三):字符串。 |
4 | 这里地方错误! x34 不会再这里出现!相关解释请看: Hessian 协议解释与实战(三):字符串。 |
4. 序列化协议
Hessian 的对象序列化支持八种基本类型:
同时,还支持三种递归类型(recursive type
):
最后,还支持一种特殊的构件:
支持共享和循环引用的 引用(
ref
)。
Hessian 2.0 又增加了三种内部引用映射:
4.1. 二进制数据
二进制语法:
// D瓜哥 · https://www.diguage.com/ · 出品 //
---------------------------------------------------------------------
binary ::= b b1 b0 <binary-data> binary
::= B b1 b0 <binary-data>
::= [x20-x2f] <binary-data>
---------------------------------------------------------------------
// D瓜哥 · https://www.diguage.com/ · 出品 //
二进制数据编码在 chunk 里面。字节 x42
(B
)表示结尾 chunk,字节 x62
(b
)表示任何非结尾 chunk。每个 chunk 有一个 16-bit 的长度值.
“ 另外,“字节 |
len = 256 * b1 + b0
4.1.1. 紧凑:简小二进制数据
对于长度小于 15 的二进制数据,可以使用一个字节的长度标识 [x20-x2f]
来进行编码。
len = code - 0x20
4.1.2. 二进制示例
// D瓜哥 · https://www.diguage.com/ · 出品 //
---------------------------------------------------------------------
x20 # zero-length binary data
x23 x01 x02 x03 # 3 octet data
B x10 x00 .... # 4k final chunk of data
b x04 x00 .... # 1k non-final chunk of data
---------------------------------------------------------------------
// D瓜哥 · https://www.diguage.com/ · 出品 //
4.1.3. 附录:解释与实践
更详细的解释与实践,请移步 Hessian 协议解释与实战(二):长整型、二进制数据与 Null:二进制数据。
4.2. 布尔型数据
布尔型语法:
// D瓜哥 · https://www.diguage.com/ · 出品 //
---------------------------------------------------------------------
boolean ::= T
::= F
---------------------------------------------------------------------
// D瓜哥 · https://www.diguage.com/ · 出品 //
字节 F
表示 false
,字节 T
表示 true
。
4.2.1. 布尔型示例
// D瓜哥 · https://www.diguage.com/ · 出品 //
---------------------------------------------------------------------
T # true
F # false
---------------------------------------------------------------------
// D瓜哥 · https://www.diguage.com/ · 出品 //
4.2.2. 附录:解释与实践
更详细的解释与实践,请移步 Hessian 协议解释与实战(一):布尔、日期、浮点数与整数:布尔型数据。
4.3. 日期类型数据
日期语法:
// D瓜哥 · https://www.diguage.com/ · 出品 //
---------------------------------------------------------------------
date ::= x4a b7 b6 b5 b4 b3 b2 b1 b0
::= x4b b4 b3 b2 b1 b0
---------------------------------------------------------------------
// D瓜哥 · https://www.diguage.com/ · 出品 //
使用以 64 bit 编码的自 epoch(1970-01-01 00:00:00 UTC)以来的毫秒数来标识日期。
4.3.1. 紧凑:以分钟表示的日期
使用以 32 bit 编码的自 epoch(1970-01-01 00:00:00 UTC)以来的分钟数来标识日期。
4.3.2. 日期示例
// D瓜哥 · https://www.diguage.com/ · 出品 //
---------------------------------------------------------------------
x4a x00 x00 x00 xd0 x4b x92 x84 xb8 # 09:51:31 May 8, 1998 UTC
x4b x4b x92 x0b xa0 # 09:51:00 May 8, 1998 UTC
---------------------------------------------------------------------
// D瓜哥 · https://www.diguage.com/ · 出品 //
4.3.3. 附录:解释与实践
更详细的解释与实践,请移步 Hessian 协议解释与实战(一):布尔、日期、浮点数与整数:日期类型数据。
4.4. 浮点类型数据
浮点数语法:
// D瓜哥 · https://www.diguage.com/ · 出品 //
---------------------------------------------------------------------
double ::= D b7 b6 b5 b4 b3 b2 b1 b0
::= x5b
::= x5c
::= x5d b0
::= x5e b1 b0
::= x5f b3 b2 b1 b0
---------------------------------------------------------------------
// D瓜哥 · https://www.diguage.com/ · 出品 //
浮点数使用 IEEE 64-bit 标准来表示。
4.4.1. 紧凑:0.0
浮点数 0.0
可以使用字节 x5b
来标识。
4.4.2. 紧凑:1.0
浮点数 1.0
可以使用字节 x5c
来标识。
4.4.3. 紧凑:单字节浮点数
对于在 -128.0 ~ 127.0 之间并且没有小数部分的浮点数,可以使用两个字节来表示;通过类型转换,将 byte
值转化为浮点数。
value = (double) b0
4.4.4. 紧凑:短整型浮点数
对于在 -32768.0 ~ 32767.0 之间并且没有小数部分的浮点数,可以使用三个字节来表示;通过类型转换,将 short
值转化为浮点数。
value = (double) (256 * b1 + b0)
4.4.5. 紧凑:单精度浮点数
与 32位浮点数等价的双精度浮点数,可以用四个字节来表示;通过类型转换,将 float
值转化为浮点数。
这里的说明非常不严谨。更具体的说明请移步: Hessian 协议解释与实战(一):布尔、日期、浮点数与整数:浮点类型数据。 |
4.4.6. 浮点类型示例
// D瓜哥 · https://www.diguage.com/ · 出品 //
---------------------------------------------------------------------
x5b # 0.0
x5c # 1.0
x5d x00 # 0.0
x5d x80 # -128.0
x5d x7f # 127.0
x5e x00 x00 # 0.0
x5e x80 x00 # -32768.0
x5e x7f xff # 32767.0
D x40 x28 x80 x00 x00 x00 x00 x00 # 12.25
---------------------------------------------------------------------
// D瓜哥 · https://www.diguage.com/ · 出品 //
4.4.7. 附录:解释与实践
更详细的解释与实践,请移步 Hessian 协议解释与实战(一):布尔、日期、浮点数与整数:浮点类型数据。
4.5. 整数类型数据
整数语法:
// D瓜哥 · https://www.diguage.com/ · 出品 //
---------------------------------------------------------------------
int ::= 'I' b3 b2 b1 b0
::= [x80-xbf]
::= [xc0-xcf] b0
::= [xd0-xd7] b1 b0
---------------------------------------------------------------------
// D瓜哥 · https://www.diguage.com/ · 出品 //
这是 32 位有符号整数。一个整数使用一个字节 x49
(I
),再跟 4 个字节且以大端法表示的数字。
value = (b3 << 24) + (b2 << 16) + (b1 << 8) + b0;
4.5.1. 紧凑:单字节整数
-16 ~ 47 的整数,可以用一个字节编码,编码范围是从 x80
到 xBF
。
value = code - 0x90
4.5.2. 紧凑:双字节整数
-2048 ~ 2047 的整数,可以用两个字节编码,并且首字节编码是从 xC0
到 xCF
。
value = ((code - 0xc8) << 8) + b0;
4.5.3. 紧凑:三字节整数
-262144 ~ 262143 的整数,可以用三个字节编码,并且首字节是从 xD0
到 xD7
。
value = ((code - 0xd4) << 16) + (b1 << 8) + b0;
4.5.4. 整数示例
// D瓜哥 · https://www.diguage.com/ · 出品 //
---------------------------------------------------------------------
x90 # 0
x80 # -16
xbf # 47
xc8 x00 # 0
xc0 x00 # -2048
xc7 x00 # -256
xcf xff # 2047
xd4 x00 x00 # 0
xd0 x00 x00 # -262144
xd7 xff xff # 262143
I x00 x00 x00 x00 # 0
I x00 x00 x01 x2c # 300
---------------------------------------------------------------------
// D瓜哥 · https://www.diguage.com/ · 出品 //
4.5.5. 附录:解释与实践
更详细的解释与实践,请移步 Hessian 协议解释与实战(一):布尔、日期、浮点数与整数:整数类型数据。
4.6. 链表数据
链表语法:
// D瓜哥 · https://www.diguage.com/ · 出品 //
---------------------------------------------------------------------
list ::= x55 type value* 'Z' # variable-length list (1)
::= 'V' type int value* # fixed-length list
::= x57 value* 'Z' # variable-length untyped list
::= x58 int value* # fixed-length untyped list
::= [x70-77] type value* # fixed-length typed list
::= [x78-7f] value* # fixed-length untyped list
---------------------------------------------------------------------
// D瓜哥 · https://www.diguage.com/ · 出品 //
1 | 没有发现这种场景,代码也不可达。 |
一个有序链表,比如数组。两种链表分别是定长链表(注:比如数组)和变长链表(注:比如 List
)。这两种链表都有一个类型。这个类型可以是一个能够被服务识别的 UTF-8 字符串。
每个列表项都被添加到引用列表中,以处理共享和循环元素。参见 ref 元素。
任何需要列表的解析器还必须接受空引用或共享引用。
类型的有效值没必要一定在本文档中指定,这取决于特定的应用程序。例如,使用带有静态类型的语言实现的公开 Hessian 服务,可以使用类型信息实例化特定的数组类型。另一方面,用动态类型语言编写的服务器可能会完全忽略类型的内容,而创建一个泛型数组。
4.6.1. 紧凑:定长链表
Hessian 2.0 允许使用紧凑形式的列表,用于预先已知长度的,类型相同的连续列表。类型和长度由整数编码,其中类型是对先前指定类型的引用。
4.6.2. 链表示例
整型数组的序列化: int[] = {0, 1} :
// D瓜哥 · https://www.diguage.com/ · 出品 //
---------------------------------------------------------------------
V # fixed length, typed list
x04 [int # encoding of int[] type
x92 # length = 2
x90 # integer 0
x91 # integer 1
---------------------------------------------------------------------
// D瓜哥 · https://www.diguage.com/ · 出品 //
无类型变长链表: list = {0, 1} :
// D瓜哥 · https://www.diguage.com/ · 出品 //
---------------------------------------------------------------------
x57 # variable-length, untyped
x90 # integer 0
x91 # integer 1
Z
---------------------------------------------------------------------
// D瓜哥 · https://www.diguage.com/ · 出品 //
定长类型:
// D瓜哥 · https://www.diguage.com/ · 出品 //
---------------------------------------------------------------------
x72 # typed list length=2
x04 [int # type for int[] (save as type #0)
x90 # integer 0
x91 # integer 1
x73 # typed list length = 3
x90 # type reference to int[] (integer #0)
x92 # integer 2
x93 # integer 3
x94 # integer 4
---------------------------------------------------------------------
// D瓜哥 · https://www.diguage.com/ · 出品 //
4.6.3. 附录:解释与实践
更详细的解释与实践,请移步 Hessian 协议解释与实战(四):数组与集合。
4.7. 长整数类型数据
长整数语法:
// D瓜哥 · https://www.diguage.com/ · 出品 //
---------------------------------------------------------------------
long ::= L b7 b6 b5 b4 b3 b2 b1 b0
::= [xd8-xef]
::= [xf0-xff] b0
::= [x38-x3f] b1 b0
::= x4c b3 b2 b1 b0
---------------------------------------------------------------------
// D瓜哥 · https://www.diguage.com/ · 出品 //
4.7.1. 紧凑:单字节长整数
-8 ~ 15 的长整数,可以用一个字节编码,并且首字节编码是从 xD8
到 xEF
。
value = (code - 0xe0)
4.7.2. 紧凑:双字节长整数
-2048 ~ 2047 的长整数,可以用两个字节编码,并且首字节编码是从 xF0
到 xFF
。
value = ((code - 0xf8) << 8) + b0
4.7.3. 紧凑:三字节长整数
-262144 ~ 262143 的长整数,可以用三个字节编码,并且首字节编码是从 x38
到 x3F
。
value = ((code - 0x3c) << 16) + (b1 << 8) + b0
4.7.4. 紧凑:四字节长整数
32 位的长整数,可以用五个字节编码,并且首字节编码为 x4C
。
value = (b3 << 24) + (b2 << 16) + (b1 << 8) + b0
这里的“首字节编码为 |
4.7.5. 示例
// D瓜哥 · https://www.diguage.com/ · 出品 //
---------------------------------------------------------------------
xe0 # 0
xd8 # -8
xef # 15
xf8 x00 # 0
xf0 x00 # -2048
xf7 x00 # -256
xff xff # 2047
x3c x00 x00 # 0
x38 x00 x00 # -262144
x3f xff xff # 262143
x4c x00 x00 x00 x00 # 0
x4c x00 x00 x01 x2c # 300
L x00 x00 x00 x00 x00 x00 x01 x2c # 300
---------------------------------------------------------------------
// D瓜哥 · https://www.diguage.com/ · 出品 //
4.7.6. 附录:解释与实践
更详细的解释与实践,请移步 Hessian 协议解释与实战(二):长整型、二进制数据与 Null:长整数类型数据。
4.8. 映射
映射语法:
// D瓜哥 · https://www.diguage.com/ · 出品 //
---------------------------------------------------------------------
map ::= M type (value value)* Z
---------------------------------------------------------------------
// D瓜哥 · https://www.diguage.com/ · 出品 //
映射的序列化模式同时也能够序列化对象。类型元素用于描述映射的类型。
这个类型可以为空,长度为零。如果没有指定类型,那么解析器可以自己选择类型。对于对象类型来说,不被识别的字段则会被忽略。
每个映射都会被添加到引用列表中。无论何时,解析器在解析映射时,必须能够兼容 null
或 引用 类型。
类型可以有服务自己选择。
4.8.1. 映射示例
一个稀疏数组:
// D瓜哥 · https://www.diguage.com/ · 出品 //
---------------------------------------------------------------------
map = new HashMap();
map.put(new Integer(1), "fee");
map.put(new Integer(16), "fie");
map.put(new Integer(256), "foe");
---
H # untyped map (HashMap for Java)
x91 # 1
x03 fee # "fee"
xa0 # 16
x03 fie # "fie"
xc9 x00 # 256
x03 foe # "foe"
Z
---------------------------------------------------------------------
// D瓜哥 · https://www.diguage.com/ · 出品 //
一个 Java 对象的映射表示:
// D瓜哥 · https://www.diguage.com/ · 出品 //
---------------------------------------------------------------------
public class Car implements Serializable {
String color = "aquamarine";
String model = "Beetle";
int mileage = 65536;
}
---
M
x13 com.caucho.test.Car # type
x05 color # color field
x0a aquamarine
x05 model # model field
x06 Beetle
x07 mileage # mileage field
I x00 x01 x00 x00
Z
---------------------------------------------------------------------
// D瓜哥 · https://www.diguage.com/ · 出品 //
4.8.2. 附录:解释与实践
最初把 map 翻译为了 哈希。后来又觉得翻译成 映射 更合适,就将“哈希”改为了“映射”。所以,如果行文中,有不一致的地方,还请海涵。 |
更详细的解释与实践,请移步 Hessian 协议解释与实战(五):对象与映射:映射。
4.9. null
null
语法:
// D瓜哥 · https://www.diguage.com/ · 出品 //
---------------------------------------------------------------------
null ::= N
---------------------------------------------------------------------
// D瓜哥 · https://www.diguage.com/ · 出品 //
null
表示一个“空”对象。
字节 N
表示这个“空”对象。
4.9.1. 附录:解释与实践
更详细的解释与实践,请移步 Hessian 协议解释与实战(二):长整型、二进制数据与 Null: null
。
4.10. 对象
对象语法:
// D瓜哥 · https://www.diguage.com/ · 出品 //
---------------------------------------------------------------------
class-def ::= 'C' string int string*
object ::= 'O' int value*
::= [x60-x6f] value*
---------------------------------------------------------------------
// D瓜哥 · https://www.diguage.com/ · 出品 //
4.10.1. 紧凑:类型定义
Hessian 2.0 有一个紧凑的对象形式,其中字段名只序列化一次。后面的对象只需要序列化它们的值。
对象定义包括强制类型字符串、字段数量和字段名称。对象定义存储在对象定义映射中,并将被对象实例使用整数引用来引用。
4.10.2. 紧凑:对象实例
Hessian 2.0 有一个紧凑的对象形式,其中字段名只序列化一次。后面的对象只需要序列化它们的值。
对象实例化是基于前面的类型定义创建一个新对象,使用整数值引用对象定义。
4.10.3. 示例
对象序列化:
// D瓜哥 · https://www.diguage.com/ · 出品 //
---------------------------------------------------------------------
class Car {
String color;
String model;
}
out.writeObject(new Car("red", "corvette"));
out.writeObject(new Car("green", "civic"));
---
C # object definition (#0)
x0b example.Car # type is example.Car
x92 # two fields
x05 color # color field name
x05 model # model field name
O # object def (long form)
x90 # object definition #0
x03 red # color field value
x08 corvette # model field value
x60 # object def #0 (short form)
x05 green # color field value
x05 civic # model field value
---------------------------------------------------------------------
// D瓜哥 · https://www.diguage.com/ · 出品 //
// D瓜哥 · https://www.diguage.com/ · 出品 //
---------------------------------------------------------------------
enum Color {
RED,
GREEN,
BLUE,
}
out.writeObject(Color.RED);
out.writeObject(Color.GREEN);
out.writeObject(Color.BLUE);
out.writeObject(Color.GREEN);
---
C # class definition #0
x0b example.Color # type is example.Color
x91 # one field
x04 name # enumeration field is "name"
x60 # object #0 (class def #0)
x03 RED # RED value
x60 # object #1 (class def #0)
x90 # object definition ref #0
x05 GREEN # GREEN value
x60 # object #2 (class def #0)
x04 BLUE # BLUE value
x51 x91 # object ref #1, i.e. Color.GREEN
---------------------------------------------------------------------
// D瓜哥 · https://www.diguage.com/ · 出品 //
4.10.4. 附录:解释与实践
更详细的解释与实践,请移步 Hessian 协议解释与实战(五):对象与映射:再谈实例对象。
4.11. 引用
引用语法:
// D瓜哥 · https://www.diguage.com/ · 出品 //
---------------------------------------------------------------------
ref ::= x51 int
---------------------------------------------------------------------
// D瓜哥 · https://www.diguage.com/ · 出品 //
在一次 Hessian 2.0 序列化过程中,已经被链表、映射或者对象实例化过的类型,可以通过一个整数数值来进行引用。当从输入流读取每个列表、映射或对象时,它被赋值为流中的整数位置,即第一个列表或映射为 0
,下一个为 1
,等等。之后的引用可以使用之前的对象。生产者可以生成引用;解析器必须能够识别它们。
引用能够关联到非完全读取的条目。例如,循环链表将在整个链表被读取之前引用第一个链接。
一种可能的实现是在读取数组时将每个映射、列表和对象添加到数组中。引用将返回数组中相应的值。为了支持循环结构,该实现将在填充内容之前,首先存储映射、列表或对象。
每个映射或列表在被解析时被存储到一个数组中。引用选择一个存储对象。第一个对象编号为 0
。
4.11.1. 引用示例
循环链表:
// D瓜哥 · https://www.diguage.com/ · 出品 //
---------------------------------------------------------------------
list = new LinkedList();
list.data = 1;
list.tail = list;
---
C
x0a LinkedList
x92
x04 head
x04 tail
o x90 # object stores ref #0
x91 # data = 1
x51 x90 # next field refers to itself, i.e. ref #0
---------------------------------------------------------------------
// D瓜哥 · https://www.diguage.com/ · 出品 //
引用仅指向链表、映射和对象元素。特别是对于字符串和二进制数据,只有当它们包装在列表或映射中时才会共享引用。
4.12. 字符串类型数据
字符串语法:
// D瓜哥 · https://www.diguage.com/ · 出品 //
---------------------------------------------------------------------
string ::= x52 b1 b0 <utf8-data> string
::= S b1 b0 <utf8-data>
::= [x00-x1f] <utf8-data>
::= [x30-x33] b0 <utf8-data>
---------------------------------------------------------------------
// D瓜哥 · https://www.diguage.com/ · 出品 //
以 UTF-8 编码的 16 位 Unicode 字符串。字符串被编码成块。x53
(S
)表示最终块,x52
(R
)表示任何非最终块。每个块有一个 16 位无符号整型长度值。
长度为 16 位字符的个数,可能与字节数不同。
字符串 chunk 可能不会拆分替代对。
“x53 (S )表示最终块”表述不正确!这个得看截取完前面的 chunk 之后,剩余的字符的个数。如果大于 1023 才会以 x53 (S )开头。相关解释请看: Hessian 协议解释与实战(三):字符串。 |
4.12.1. 紧凑:短字符串
长度小于 32 的字符串可以用一个字节长度编码 [x00-x1f]
。
value = code
4.12.2. 字符串示例
// D瓜哥 · https://www.diguage.com/ · 出品 //
---------------------------------------------------------------------
x00 # "", empty string
x05 hello # "hello"
x01 xc3 x83 # "\u00c3"
S x00 x05 hello # "hello" in long form
x52 x00 x07 hello, # "hello, world" split into two chunks
x05 world # 注:这里是最终块,为啥没有用 S 开头呢?
# 上面的示例中,使用 S 开头,而这里却用 x52 开头,格式上非常不统一。感觉很奇怪!
---------------------------------------------------------------------
// D瓜哥 · https://www.diguage.com/ · 出品 //
4.12.3. 附录:解释与实践
更详细的解释与实践,请移步 Hessian 协议解释与实战(三):字符串。
4.13. 类型
类型语法:
// D瓜哥 · https://www.diguage.com/ · 出品 //
---------------------------------------------------------------------
type ::= string
::= int
---------------------------------------------------------------------
// D瓜哥 · https://www.diguage.com/ · 出品 //
任何一个类型都会被加入到 类型引用 中,以便将来引用。
4.14. 压缩:类型引用
重复的类型字符串可以使用 类型引用 来引用以前使用的类型。解析期间,对于所有的类型,类型引用都是从零开始的。
5. 引用映射
6. 字节码映射
Hessian 被组织为字节码协议。Hessian 反序列化本质上是对其实字节的 switch
语句。
字节码编码:
// D瓜哥 · https://www.diguage.com/ · 出品 //
---------------------------------------------------------------------
x00 - x1f # utf-8 string length 0-31 (1)
x20 - x2f # binary data length 0-15 (2)
x30 - x33 # utf-8 string length 32-1023 (3) (5)
x34 - x37 # binary data length 16-1023 (4) (5)
x38 - x3f # three-octet compact long (-x40000 to x3ffff) (5)
x40 # reserved (expansion/escape)
x41 # 8-bit binary data non-final chunk ('A')
x42 # 8-bit binary data final chunk ('B')
x43 # object type definition ('C')
x44 # 64-bit IEEE encoded double ('D')
x45 # reserved
x46 # boolean false ('F')
x47 # reserved
x48 # untyped map ('H')
x49 # 32-bit signed integer ('I')
x4a # 64-bit UTC millisecond date
x4b # 32-bit UTC minute date
x4c # 64-bit signed long integer ('L')
x4d # map with type ('M')
x4e # null ('N')
x4f # object instance ('O')
x50 # reserved
x51 # reference to map/list/object - integer ('Q')
x52 # utf-8 string non-final chunk ('R')
x53 # utf-8 string final chunk ('S')
x54 # boolean true ('T')
x55 # variable-length list/vector ('U')
x56 # fixed-length list/vector ('V')
x57 # variable-length untyped list/vector ('W')
x58 # fixed-length untyped list/vector ('X')
x59 # long encoded as 32-bit int ('Y')
x5a # list/map terminator ('Z')
x5b # double 0.0
x5c # double 1.0
x5d # double represented as byte (-128.0 to 127.0)
x5e # double represented as short (-32768.0 to 327676.0)
x5f # double represented as float
x60 - x6f # object with direct type
x70 - x77 # fixed list with direct length
x78 - x7f # fixed untyped list with direct length
x80 - xbf # one-octet compact int (-x10 to 47, x90 is 0) (6)
xc0 - xcf # two-octet compact int (-x800 to x7ff) (5)
xd0 - xd7 # three-octet compact int (-x40000 to x3ffff) (5)
xd8 - xef # one-octet compact long (-x8 to xf, xe0 is 0)
xf0 - xff # two-octet compact long (-x800 to x7ff, xf8 is 0) (5)
---------------------------------------------------------------------
// D瓜哥 · https://www.diguage.com/ · 出品 //
1 | 由 32 改为 31 ; |
2 | 由 16 改为 15 ; |
3 | 由 0 改为 32 ; |
4 | 由 0 改为 16 ; |
5 | 具体范围划分,请看下面的图表; |
6 | 由 x3f 改为 47 ; |
后记
经过多天断断续续的尝试,终于在“无疫节”当天,把这篇协议给翻译完了。坦白讲,我觉得有些稀里糊涂。一方便是D瓜哥自身英语水平所限;另外一方面,Hessian 协议有很多言语不详之处,有很多不做实验,根本搞不清楚它说的是啥意思。如有问题,欢迎反馈。
为了便于理解 Hessian 协议,在网上找了找 Hessian 的源码库,似乎源码没有开源。在 Hessian Binary Web Service Protocol 中,提供了 Java 各个版本的源码包,为了方便调试,D瓜哥将其源码下载下来,然后推送到了 GitHub 上: diguage/hessian,由于是解压的源码包,所以这里没有提交记录,只有各个已经发布版本对应的源代码。感兴趣,也欢迎 Fork。
后续,D瓜哥还会做一些实验,来帮助理解这个协议,敬请期待。