Hessian 2.0 序列化协议(中文版)

Hessian 2.0 序列化协议(中文版)

公司在微服务系统中,序列化协议大多数使用 MessagePack。但是,由于 MessagePack 设计限制,导致微服务接口在增减参数时,只能在最后操作。但是,由于个人操作,难免失误,结果造成因为增减字段导致的事故层出不穷。最近,一些条件成熟,准备推动部门将序列化协议切换到 Hessian。

原以为,切换到 Hessian 就可以万事大吉。但是,在和同事的沟通中发现,同事反馈,Hessian 本身也有一些限制。为了对 Hessian 有一个更深入的了解,干脆就把 Hessian 序列化协议读一遍。看协议,文字不多,干脆就把协议完整翻译一遍。闲言少叙,正文开始。


Hessian 2.0 序列化协议

协议解释

针对该协议有很多言语不详,甚至模糊不清之处,专门做了一些解释和实践,叙述系列文章,用于辅助消化理解。目录如下:

  1. Hessian 协议解释与实战(一):布尔、日期、浮点数与整数 — 介绍布尔型数据、日期类型、浮点类型数据和整数类型数据等四种类型的数据的处理。

  2. Hessian 协议解释与实战(二):长整型、二进制数据与 Null — 介绍长整数类型数据、二进制数据和 null 等三种类型的数据的处理。

  3. Hessian 协议解释与实战(三):字符串 — 专门介绍了关于字符串的处理。由于字符串需要铺垫的基础知识比较多,处理细节也有繁琐,所以单独成篇来介绍。

  4. Hessian 源码分析(Java) — 开始第四篇分析之前,先来介绍一下 Hessian 的源码实现。方便后续展开说明。

  5. Hessian 协议解释与实战(四):数组与集合 — 铺垫了一些关于实例对象的处理,重点介绍关于数组和集合的相关处理。

  6. Hessian 协议解释与实战(五):对象与映射 — 重点介绍关于对象与映射的相关处理。

  7. 未完待续,敬请继续关注 "地瓜哥"博客网

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 的对象序列化支持八种基本类型:

  1. 原始 二进制数据binary

  2. 布尔型数据boolean

  3. 64位 毫秒 日期类型数据date

  4. 64位 双精度 浮点类型数据double

  5. 32位 整数类型数据int

  6. 64位 长整数类型数据long

  7. nullnull

  8. UTF-8 编码的 字符串类型数据string

同时,还支持三种递归类型(recursive type):

  1. 支持链表(list)和数组(array)的 链表数据list

  2. 支持映射(map)和字典(dictionary)的 映射map

  3. 支持对象的 对象object)。

最后,还支持一种特殊的构件:

  1. 支持共享和循环引用的 引用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 里面。字节 x42B)表示结尾 chunk,字节 x62b)表示任何非结尾 chunk。每个 chunk 有一个 16-bit 的长度值.

x42B)表示结尾 chunk”表述不正确!这个得看截取完前面的 chunk 之后,剩余的字符的个数。如果大于 1023 才会以 x42B)开头。

另外,“字节 x62b)表示任何非结尾 chunk”的也不正确。根据实际测试来看,应该是 0x41A)。

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. 附录:解释与实践

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. 附录:解释与实践

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. 附录:解释与实践

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. 附录:解释与实践

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 位有符号整数。一个整数使用一个字节 x49I),再跟 4 个字节且以大端法表示的数字。

value = (b3 << 24) + (b2 << 16) + (b1 << 8) + b0;

4.5.1. 紧凑:单字节整数

-16 ~ 47 的整数,可以用一个字节编码,编码范围是从 x80xBF

value = code - 0x90

4.5.2. 紧凑:双字节整数

-2048 ~ 2047 的整数,可以用两个字节编码,并且首字节编码是从 xC0xCF

value = ((code - 0xc8) << 8) + b0;

4.5.3. 紧凑:三字节整数

-262144 ~ 262143 的整数,可以用三个字节编码,并且首字节是从 xD0xD7

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. 附录:解释与实践

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 服务,可以使用类型信息实例化特定的数组类型。另一方面,用动态类型语言编写的服务器可能会完全忽略类型的内容,而创建一个泛型数组。

D瓜哥注

参考 Hessian 协议解释与实战(四):数组与集合 会发现,在 Hessian 的协议中,虽然英文是 list,但这里的内涵更仿佛,还包括数组、 Set 和迭代器。感觉翻译成“集合”更合适。

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 的长整数,可以用一个字节编码,并且首字节编码是从 xD8xEF

value = (code - 0xe0)

4.7.2. 紧凑:双字节长整数

-2048 ~ 2047 的长整数,可以用两个字节编码,并且首字节编码是从 xF0xFF

value = ((code - 0xf8) << 8) + b0

4.7.3. 紧凑:三字节长整数

-262144 ~ 262143 的长整数,可以用三个字节编码,并且首字节编码是从 x38x3F

value = ((code - 0x3c) << 16) + (b1 << 8) + b0

4.7.4. 紧凑:四字节长整数

32 位的长整数,可以用五个字节编码,并且首字节编码为 x4C

value = (b3 << 24) + (b2 << 16) + (b1 << 8) + b0

这里的“首字节编码为 x4C”是错误的,正确的应该是 0x59

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. 附录:解释与实践

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 翻译为了 哈希。后来又觉得翻译成 映射 更合适,就将“哈希”改为了“映射”。所以,如果行文中,有不一致的地方,还请海涵。

4.9. null

null 语法:

//               D瓜哥 · https://www.diguage.com/ · 出品            //
---------------------------------------------------------------------

null ::= N

---------------------------------------------------------------------
//               D瓜哥 · https://www.diguage.com/ · 出品            //

null 表示一个“空”对象。

字节 N 表示这个“空”对象。

4.9.1. 附录:解释与实践

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. 附录:解释与实践

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 字符串。字符串被编码成块。x53S)表示最终块,x52R)表示任何非最终块。每个块有一个 16 位无符号整型长度值。

长度为 16 位字符的个数,可能与字节数不同。

字符串 chunk 可能不会拆分替代对。

x53S)表示最终块”表述不正确!这个得看截取完前面的 chunk 之后,剩余的字符的个数。如果大于 1023 才会以 x53S)开头。相关解释请看: 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. 引用映射

Hessian 2.0 有三个内置的引用映射:

  1. 一个 映射/对象/链表 引用映射。

  2. 一个类定义映射。

  3. 一个类型(类名)映射。

值引用映射允许 Hessian 支持任意图,递归和循环数据结构。

类和类型映射通过避免常见字符串数据的重复来提高 Hessian 效率。

5.1. 值引用

当 Hessian 在字节码流中遇到任意图形时,它通过添加 链表数据对象映射 来支持这些图形。

解析器必须在遇到每个列表、对象和映射时,必须将它们存储在引用映射中。

存储的对象可以与 引用 字节码一起使用。

5.2. 类引用

每个 对象定义 都会自动添加到类映射中。解析器必须在遇到类定义时向类映射添加类定义。后面的对象实例将引用已被定义的类。

5.3. 类型引用

映射链表数据 值的类型字符串存储在类型映射中以供参考。

解析器必须在遇到类型字符串时向类型映射添加类型字符串。

6. 字节码映射

Hessian 被组织为字节码协议。Hessian 反序列化本质上是对其实字节的 switch 语句。

字节码编码:

//               D瓜哥 · https://www.diguage.com/ · 出品            //
---------------------------------------------------------------------

x00 - x1f    # utf-8 string length 0-32
x20 - x2f    # binary data length 0-16
x30 - x33    # utf-8 string length 0-1023
x34 - x37    # binary data length 0-1023
x38 - x3f    # three-octet compact long (-x40000 to x3ffff)
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 x3f, x90 is 0)
xc0 - xcf    # two-octet compact int (-x800 to x7ff)
xd0 - xd7    # three-octet compact int (-x40000 to x3ffff)
xd8 - xef    # one-octet compact long (-x8 to xf, xe0 is 0)
xf0 - xff    # two-octet compact long (-x800 to x7ff, xf8 is 0)

---------------------------------------------------------------------
//               D瓜哥 · https://www.diguage.com/ · 出品            //

后记

经过多天断断续续的尝试,终于在“无疫节”当天,把这篇协议给翻译完了。坦白讲,我觉得有些稀里糊涂。一方便是D瓜哥自身英语水平所限;另外一方面,Hessian 协议有很多言语不详之处,有很多不做实验,根本搞不清楚它说的是啥意思。如有问题,欢迎反馈。

为了便于理解 Hessian 协议,在网上找了找 Hessian 的源码库,似乎源码没有开源。在 Hessian Binary Web Service Protocol 中,提供了 Java 各个版本的源码包,为了方便调试,D瓜哥将其源码下载下来,然后推送到了 GitHub 上: diguage/hessian,由于是解压的源码包,所以这里没有提交记录,只有各个已经发布版本对应的源代码。感兴趣,也欢迎 Fork。

后续,D瓜哥还会做一些实验,来帮助理解这个协议,敬请期待。