Thrift IDL基于Thrift Types,并且考虑到Thrift Types的定义,Thrift IDL文件由Thrift代码生成器处理,为各种目标语言生成代码,以支持IDL文件中定义的结构和服务。
一、Thrift类型系统(Thrift Types)
Thrift类型系统致力于让程序员尽可能的使用原生数据类型,而不用关心他们所使用的编程语言。
1、基本类型
bool: A boolean value (true or false)
byte: An 8-bit signed integer
i16: A 16-bit signed integer
i32: A 32-bit signed integer
i64: A 64-bit signed integer
double: A 64-bit floating point number
string: A text string encoded using UTF-8 encoding
注意没有无符号整数类型。这是因为在许多编程语言中没有无符号整数类型。
2、特殊类型
binary: a sequence of unencoded bytes
这是上面所提到的字符串类型的一种特殊形式,用于提供与Java更好的互操作性
3、结构体
结构体定义一个通用对象。本质上等价于OOP语言中的类,但没有继承。结构体有一组强类型字段,每个字段具有唯一的名称标识符。字段可以具有在Thrift IDL中描述的各种注释(数字字段id、可选的默认值等)
4、容器
容器是强类型容器,在大多数编程语言中能映射到常用的容器类型。
有三种容器类型:
list:元素的有序列表。翻译成STL vector、Java ArrayList、脚本语言中的本机数组等
set:唯一元素的无序集合。翻译成STL set、Java HashSet、Python中的set等。注意:PHP不支持集合,所以它与列表类似
map:值的唯一键的映射。翻译成STL map、Java HashMap、PHP关联数组、Python/Ruby字典等,虽然提供了默认值,但是类型映射不是显式固定的,已经添加了自定义代码生成器指令,以允许在各种目标语言中替换自定义类型。
容器元素可以是任何有效的Thrift类型。
为了最大的兼容性,map的键类型应该是基本类型,而不是结构体或容器类型。有些语言不支持本地映射类型中的更复杂的键类型。此外,JSON协议只支持基类型的关键类型。
5、异常
异常在功能上等同于结构,除了它们在每个目标编程语言中适当地从本机异常基类继承,以便在任何给定语言中无缝地与本机异常处理集成。
6、服务
服务是使用Thirft类型定义的。服务的定义在语义上等同于在面向对象编程中定义接口(或纯虚拟抽象类)。Thrift编译器生成实现接口的全功能客户端和服务器代码桩(stubs)。
服务由一组命名函数组成,每个函数都有一个参数列表和一个返回类型。
注意,除了所有其他定义的Thrift类型之外,void是函数返回的有效类型。此外,可以向void函数添加oneway修饰符关键字,该函数将生成不等待响应的代码。注意,纯void函数将返回一个响应给客户端,该响应保证操作已经在服务器端完成。用oneway方法调用客户端只能保证请求在传输层成功。同一客户机的oneway方法调用可以由服务器在并行/乱序的情况下执行。
二、Thrift接口描述语言
1、Document
每个Thrift文档包含0或更多的头,后面跟着0或更多的定义。
[1] Document ::= Header* Definition*
2、Header
header是一个Thrift include,一个c++ include,或者一个名称空间声明。
[2] Header ::= Include | CppInclude | Namespace
(1) Thrift Include
include使来自另一个文件的所有符号都可见(带有前缀),并将相应的include语句添加到为此Thrift文档生成的代码中。
[3] Include ::= 'include' Literal
(2)C++ Include
c++ include中添加了一个自定义c++程序,包括用于这个Thrift文档的c++代码生成器的输出。
[4] CppInclude ::= 'cpp_include' Literal
3、Namespace
名称空间(namespace)声明哪个名称空间(namespace)/包(package)/模块(module)等。此文件中的类型定义将为目标语言声明。名称空间范围指示该名称空间应用于哪种语言;“*”的范围指示名称空间适用于所有目标语言。
[5] Namespace ::= ( 'namespace' ( NamespaceScope Identifier ) |
( 'smalltalk.category' STIdentifier ) |
( 'smalltalk.prefix' Identifier ) ) |
( 'php_namespace' Literal ) |
( 'xsd_namespace' Literal )
[6] NamespaceScope ::= '*' | 'cpp' | 'java' | 'py' | 'perl' | 'rb' | 'cocoa' | 'csharp'
4、Definition
[7] Definition ::= Const | Typedef | Enum | Senum | Struct | Union | Exception | Service
5、Const
[8] Const ::= 'const' FieldType Identifier '=' ConstValue ListSeparator?
6、Typedef
类型定义为类型创建另一个名称。
[9] Typedef ::= 'typedef' DefinitionType Identifier
7、Enum
枚举创建带有命名值的枚举类型。如果没有提供常量值,则第一个元素的值为0,或任何后续元素的值大于前一个值。所提供的任何常数值都必须是非负的。
[10] Enum ::= 'enum' Identifier '{' (Identifier ('=' IntConstant)? ListSeparator?)* '}'
8、Senum
Senum(和Slist)现在已被弃用,应该用字符串替换它们。
[11] Senum ::= 'senum' Identifier '{' (Literal ListSeparator?)* '}'
9、Struct
结构体是Thrift的基本组成类型。每个字段的名称必须在结构体中是唯一的。
[12] Struct ::= 'struct' Identifier 'xsd_all'? '{' Field* '}'
10、Union
union类似于struct,只不过它们提供了一种方法来传输一组可能的字段,就像c++中的union{}一样。因此,联合成员被隐式地认为是可选的(请参阅require)。
[13] Union ::= 'union' Identifier 'xsd_all'? '{' Field* '}'
11、Exception
异常与结构相似,除了它们旨在与目标语言中的本机异常处理机制集成。每个字段的名称必须在异常中是唯一的。
[14] Exception ::= 'exception' Identifier '{' Field* '}'
12、Service
服务为Thrift server提供了接口。接口只是一个函数列表。服务可以扩展另一个服务,这仅仅意味着它除了提供自己的服务之外还提供扩展服务的功能。
[15] Service ::= 'service' Identifier ( 'extends' Identifier )? '{' Function* '}'
13、Field
[16] Field ::= FieldID? FieldReq? FieldType Identifier ('= ConstValue)? XsdFieldOptions ListSeparator?
14、Field ID
[17] FieldID ::= IntConstant ':'
15、Field Requiredness
有两个显式的Requiredness值,第三个是应用的含义(如果既不是必需的也不是可选的):默认Requiredness。
[18] FieldReq ::= 'required' | 'optional'
Requiredness的一般规则如下:
required
Write: 字段总是被写入,并且期望被设置。
Read: 字段总是被读取,并且期望包含在输入流中。
Defaults values:总是被写入。
如果在读取过程中缺少一个required字段,那么预期的行为是向调用者指出一个失败的读取操作,例如抛出异常或返回错误。
由于这种行为,required字段极大地限制了软版本控制的选项。因为它们必须在读取时显示,所以不能废弃字段。如果需要的字段被删除(或更改为可选字段),则版本之间的数据不再兼容。
optional
Write: 字段只在设置时才写入。
Read: 字段可能是,也可能不是输入流的一部分。
Defaults values:在设置isset标志时被写入。
大多数语言实现都使用所谓的“isset”标志,以指示是否设置了特定的可选字段。只有具有此标志的字段才被写入,相反,只有在从输入流读取字段值时才会设置标志。
default requiredness (implicit)
Write: 在理论上,字段总是被写入。这个规则有一些例外,见下文。
Read: 与可选字段一样,字段可能是或不是输入流的一部分。
Defaults values:可能不写(见下一节)
默认需求是一个很好的起点。所期望的行为是可选的和必需的混合,因此内部名称为“opt-in, req-out”。虽然理论上这些字段应该是写出来的(“req-out”),但在现实中,未设置的字段并不总是写出来的。特别是当字段包含一个不能通过Thrift进行传输的值时。这一点的唯一方法是完全不编写这个字段,而这正是大多数语言所做的。
默认值的语义(Semantics of Default Values)
关于这个话题还有很多讨论。并不是所有的实现都以相同的方式处理默认值,但是当前的状态或多或少是默认字段通常在初始化时设置的。因此,可能不会写入等于默认值的值,因为读端将隐式地设置值。另一方面,无论如何,实现都可以自由地写入默认值,因为没有硬限制可以防止这种情况。
这里要记住的主要一点是,任何未写入的默认值都隐式地成为接口版本的一部分。如果这个默认值改变了,那么接口就会改变。相反,如果将默认值写入输出数据,则IDL中的默认值可以随时更改,而不会影响序列化数据。
16、Functions
[21] Function ::= 'oneway'? FunctionType Identifier '(' Field* ')' Throws? ListSeparator?
[22] FunctionType ::= FieldType | 'void'
[23] Throws ::= 'throws' '(' Field* ')'
17、Types
[24] FieldType ::= Identifier | BaseType | ContainerType
[25] DefinitionType ::= BaseType | ContainerType
[26] BaseType ::= 'bool' | 'byte' | 'i8' | 'i16' | 'i32' | 'i64' | 'double' | 'string' | 'binary' | 'slist'
[27] ContainerType ::= MapType | SetType | ListType
[28] MapType ::= 'map' CppType? '<' FieldType ',' FieldType '>'
[29] SetType ::= 'set' CppType? '<' FieldType '>'
[30] ListType ::= 'list' '<' FieldType '>' CppType?
[31] CppType ::= 'cpp_type' Literal
18、Constant Values
[32] ConstValue ::= IntConstant | DoubleConstant | Literal | Identifier | ConstList | ConstMap
[33] IntConstant ::= ('+' | '-')? Digit+
[34] DoubleConstant ::= ('+' | '-')? Digit* ('.' Digit+)? ( ('E' | 'e') IntConstant )?
[35] ConstList ::= '[' (ConstValue ListSeparator?)* ']'
[36] ConstMap ::= '{' (ConstValue ':' ConstValue ListSeparator?)* '}'
19、基本定义
Literal
[37] Literal ::= ('"' [^"]* '"') | ("'" [^']* "'")
Identifier
[38] Identifier ::= ( Letter | '_' ) ( Letter | Digit | '.' | '_' )*
[39] STIdentifier ::= ( Letter | '_' ) ( Letter | Digit | '.' | '_' | '-' )*
List Separator
[40] ListSeparator ::= ',' | ';'
Letters and Digits
[41] Letter ::= ['A'-'Z'] | ['a'-'z']
[42] Digit ::= ['0'-'9']