接口描述语言(IDL)
Thrift 接口描述语言
适用于 Thrift 0.25.0 版本。
Thrift 接口定义语言(IDL)用于定义 Thrift 类型。Thrift IDL 文件由 Thrift 代码生成器处理,从而为各种目标语言生成代码,以支持该 IDL 文件中定义的结构体和服务。
描述
以下是对 Thrift IDL 的说明。
文档
每个 Thrift 文档都包含 0 个或多个头部,其后跟着 0 个或多个定义。
[1] Document ::= Header* Definition*头部可以是 Thrift include、C++ include 或命名空间声明。
[2] Header ::= Include | CppInclude | NamespaceThrift include
include 会将另一个文件中的所有符号(带前缀)引入可见范围,并在为此 Thrift 文档生成的代码中添加相应的 include 语句。
[3] Include ::= 'include' LiteralC++ Include
C++ include 会为该 Thrift 文档的 C++ 代码生成器的输出添加一条自定义的 C++ include 指令。
[4] CppInclude ::= 'cpp_include' Literal命名空间
命名空间用于声明本文件中的类型定义在目标语言中将被声明到哪个命名空间/包/模块等之中。命名空间的范围(scope)指明该命名空间适用于哪种语言;范围为 '*' 表示该命名空间适用于所有目标语言。
[5] Namespace ::= ( 'namespace' ( NamespaceScope Identifier ) )
[6] NamespaceScope ::= '*' | 'c_glib' | 'cpp' | 'delphi' | 'haxe' | 'go' | 'java' | 'js' | 'lua' | 'netstd' | 'perl' | 'php' | 'py' | 'py.twisted' | 'rb' | 'st' | 'xsd'定义
[7] Definition ::= Const | Typedef | Enum | Struct | Union | Exception | Service常量
[8] Const ::= 'const' FieldType Identifier '=' ConstValue ListSeparator?类型定义(typedef)
类型定义用于为某个类型创建一个别名。
[9] Typedef ::= 'typedef' DefinitionType Identifier枚举(Enum)
枚举用于创建具名值的枚举类型。若未提供常量值,则第一个元素的值为 0,其后每个元素的值比前一个元素的值大 1。所提供的任何常量值必须为非负值。
[10] Enum ::= 'enum' Identifier '{' (Identifier ('=' IntConstant)? ListSeparator?)* '}'结构体(Struct)
结构体是 Thrift 中的基本组合类型。每个字段的名称在该结构体内必须唯一。
[11] Struct ::= 'struct' Identifier 'xsd_all'? '{' Field* '}'注意:xsd_all 关键字在 Facebook 内部有某些用途,但在 Thrift 本身中并无作用。强烈不建议使用此特性。
Union(联合体)
联合体与结构体类似,不同之处在于它提供了一种方式,用于传输一组可能字段中恰好一个字段的值,就像 C++ 中的 union {} 一样。因此,联合体的成员被视为隐式可选的(参见必需性)。
[12] Union ::= 'union' Identifier 'xsd_all'? '{' Field* '}'注:xsd_all 关键字在 Facebook 内部有其特定用途,但在 Thrift 本身中并无作用。强烈不建议使用此特性。
异常(Exception)
异常与结构体类似,区别在于异常旨在与目标语言的原生异常处理机制相集成。异常中每个字段的名称必须唯一。
[13] Exception ::= 'exception' Identifier '{' Field* '}'服务(Service)
服务为 Thrift 服务器提供的一组功能定义了接口。该接口本质上就是一份函数列表。服务可以继承另一个服务,这意味着它除了提供自身的函数外,还提供被继承服务中的函数。
[14] Service ::= 'service' Identifier ( 'extends' Identifier )? '{' Function* '}'字段
[15] Field ::= FieldID? FieldReq? FieldType Identifier ('=' ConstValue)? XsdFieldOptions ListSeparator?字段 ID
[16] FieldID ::= IntConstant ':'字段必填性
字段有两种显式的必填性取值,如果既未指定 required 也未指定 optional,则会隐式应用第三种取值:default(默认)必填性。
[17] FieldReq ::= 'required' | 'optional'必选性(requiredness)的一般规则如下:
required(必填)
- 写入:必填字段始终被写入,并且期望已被设置。
- 读取:必填字段始终被读取,并且期望包含在输入流中。
- 默认值:始终写入
如果在读取时缺少某个必填字段,预期的行为是向调用方指示读取操作失败,例如抛出异常或返回错误。
由于这种行为,必填字段会极大地限制软版本化(soft versioning)的可选方案。因为读取时必须存在,这些字段无法被弃用。如果移除某个必填字段(或将其改为 optional),不同版本之间的数据就不再兼容。
optional(可选)
- 写入:可选字段仅在被设置时才写入
- 读取:可选字段可能存在于输入流中,也可能不存在。
- 默认值:在 isset 标志被置位时写入
大多数语言实现都采用推荐的做法,即使用所谓的 “isset” 标志来指示某个特定的可选字段是否已被设置。只有该标志被置位的字段才会被写入;反过来,只有当字段值已从输入流中读取时,该标志才会被置位。
默认必选性(隐式)
- 写入:理论上,这些字段始终被写入。但存在一些例外情况,见下文。
- 读取:与 optional 相同,该字段可能存在于输入流中,也可能不存在。
- 默认值:可能不被写入(见下一节)
默认必选性是一个很好的起点。期望的行为是 optional 与 required 的混合,因此其内部名称为 “opt-in, req-out”(读入时可选,写出时必填)。尽管理论上这些字段应当被写出(“req-out”),但实际上未设置的字段并不总是被写入。尤其当字段包含的值按定义无法通过 Thrift 传输时更是如此。实现这一点的唯一办法就是完全不写入该字段,大多数语言正是这样做的。
默认值的语义
关于这一主题仍在讨论中,详情参见 JIRA。并非所有实现对默认值的处理方式都完全相同,但目前的现状大体上是:默认字段通常在初始化时设置。因此,等于默认值的值可能不会被写入,因为读取端会隐式地设置该值。另一方面,实现也可以自由地写入默认值,因为并没有硬性限制阻止这样做。
这里需要记住的要点是:任何未写出的默认值都会隐式地成为接口版本的一部分。如果该默认值被修改,接口即发生变化。相反,如果默认值被写入输出数据,则 IDL 中的默认值可以随时更改,而不会影响序列化数据。
XSD 选项
注意:这些选项在 Facebook 内部有某些用途,但在 Thrift 中目前没有任何用途。强烈建议不要使用这些选项。
[18] XsdFieldOptions ::= 'xsd_optional'? 'xsd_nillable'? XsdAttrs?
[19] XsdAttrs ::= 'xsd_attrs' '{' Field* '}'函数
[20] Function ::= 'oneway'? FunctionType Identifier '(' Field* ')' Throws? ListSeparator?
[21] FunctionType ::= FieldType | 'void'
[22] Throws ::= 'throws' '(' Field* ')'类型
[23] FieldType ::= Identifier | BaseType | ContainerType
[24] DefinitionType ::= BaseType | ContainerType
[25] BaseType ::= 'bool' | 'byte' | 'i8' | 'i16' | 'i32' | 'i64' | 'double' | 'string' | 'binary' | 'uuid'
[26] ContainerType ::= MapType | SetType | ListType
[27] MapType ::= 'map' CppType? '<' FieldType ',' FieldType '>'
[28] SetType ::= 'set' CppType? '<' FieldType '>'
[29] ListType ::= 'list' CppType? '<' FieldType '>'
[30] CppType ::= 'cpp_type' Literal常量值
[31] ConstValue ::= IntConstant | DoubleConstant | Literal | Identifier | ConstList | ConstMap
[32] IntConstant ::= ('+' | '-')? Digit+
[33] DoubleConstant ::= ('+' | '-')? Digit* ('.' Digit+)? ( ('E' | 'e') IntConstant )?
[34] ConstList ::= '[' (ConstValue ListSeparator?)* ']'
[35] ConstMap ::= '{' (ConstValue ':' ConstValue ListSeparator?)* '}'基本定义
字面量
[36] Literal ::= ('"' [^"]* '"') | ("'" [^']* "'")标识符
[37] Identifier ::= ( Letter | '_' ) ( Letter | Digit | '.' | '_' )*
[38] STIdentifier ::= ( Letter | '_' ) ( Letter | Digit | '.' | '_' | '-' )*列表分隔符
[39] ListSeparator ::= ',' | ';'字母与数字
[40] Letter ::= ['A'-'Z'] | ['a'-'z']
[41] Digit ::= ['0'-'9']保留关键字
"BEGIN", "END", "__CLASS__", "__DIR__", "__FILE__", "__FUNCTION__",
"__LINE__", "__METHOD__", "__NAMESPACE__", "abstract", "alias", "and", "args", "as",
"assert", "begin", "break", "case", "catch", "class", "clone", "continue", "declare",
"def", "default", "del", "delete", "do", "dynamic", "elif", "else", "elseif", "elsif",
"end", "enddeclare", "endfor", "endforeach", "endif", "endswitch", "endwhile", "ensure",
"except", "exec", "finally", "float", "for", "foreach", "from", "function", "global",
"goto", "if", "implements", "import", "in", "inline", "instanceof", "interface", "is",
"lambda", "module", "native", "new", "next", "nil", "not", "or", "package", "pass",
"public", "print", "private", "protected", "raise", "redo", "rescue", "retry", "register",
"return", "self", "sizeof", "static", "super", "switch", "synchronized", "then", "this",
"throw", "transient", "try", "undef", "unless", "unsigned", "until", "use", "var",
"virtual", "volatile", "when", "while", "with", "xor", "yield"示例
以下是一些使用 Thrift IDL 编写的 Thrift 定义示例:
- Thrift 测试框架所使用的 ThriftTest.thrift
- Thrift 教程
- Facebook 的 fb303.thrift
- Apache Cassandra 的 Thrift IDL:cassandra.thrift
- Evernote API
待办事项/问题
所有语言中基本类型的初始化方式?
- 是否所有语言都将其初始化为 0、bool=false 和 string=""?还是 null、undefined?
为什么 CppType 在 SetType 与 ListType 中的位置不同?
- std::set 会自动对元素进行排序,这是它的设计。更多细节请参见 Thrift 类型或 C++ std:set 参考文档
- 问题是,其他语言是如何处理的?自定义对象呢,它们是否有 Compare 函数来正确设定顺序?
为什么 DefinitionType 不能与 FieldType 相同(即包含 Identifier)?
研究 smalltalk.prefix 和 smalltalk.category 的状态(尤其是 smalltalk.category,它以 STIdentifier 作为参数)……
ListSeparator 该如何处理?我们真的要像现在这样宽松吗?
Struct、Enum 等中的 Field* 是否真的应该是 Field+?
评论
登录后参与评论
KnowForge