Thrift入门 | 青训营笔记

171 阅读6分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 6 天

Thrift 接口定义语言 (IDL) 允许定义 Thrift 类型。 Thrift IDL 文件由 Thrift 代码生成器处理,为各种目标语言生成代码,以支持 IDL 文件中定义的结构和服务。

Document

每个 Thrift 文档都包含 0 个或多个标题,后跟 0 个或多个定义。

[1]  Document        ::=  Header* Definition*

Header

标头可以是 Thrift 包含、C++ 包含或名称空间声明。

[2]  Header          ::=  Include | CppInclude | Namespace

Thrift Include

include 使来自另一个文件的所有符号可见(带有前缀),并将相应的 include 语句添加到为此 Thrift 文档生成的代码中。

[3]  Include         ::=  'include' Literal

C++ Include

C++ 包含将自定义 C++ 包含添加到此 Thrift 文档的 C++ 代码生成器的输出。

[4]  CppInclude      ::=  'cpp_include' Literal

Namespace

命名空间声明了哪些命名空间/包/模块/等。此文件中的类型定义将在目标语言中声明。命名空间范围表示命名空间适用于哪种语言; “*”的范围表示命名空间适用于所有目标语言。

[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'

Definition

[7]  Definition      ::=  Const | Typedef | Enum | Struct | Union | Exception | Service

Const

[8]  Const           ::=  'const' FieldType Identifier '=' ConstValue ListSeparator?

Typedef

typedef 为类型创建备用名称。

[9]  Typedef         ::=  'typedef' DefinitionType Identifier

Enum

枚举创建具有命名值的枚举类型。如果未提供常量值,则第一个元素的值为 0,或者任何后续元素的值大于前一个值。提供的任何常数值都必须是非负数。

[10] Enum            ::=  'enum' Identifier '{' (Identifier ('=' IntConstant)? ListSeparator?)* '}'

Struct

结构是 Thrift 中的基本组合类型。每个字段的名称在结构中必须是唯一的。

[11] Struct          ::=  'struct' Identifier 'xsd_all'? '{' Field* '}'

注意:xsd_all 关键字在 Facebook 内部有一些用途,但在 Thrift 本身没有任何用途。强烈建议不要使用此功能

Union

联合类似于结构,不同之处在于它们提供了一种方法来准确地传输一组可能的字段中的一个字段,就像 C++ 中的联合 {} 一样。因此,联合成员被隐含地认为是可选的(参见要求)。

[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* '}'

Field

[15] Field           ::=  FieldID? FieldReq? FieldType Identifier ('=' ConstValue)? XsdFieldOptions ListSeparator?

Field ID

[16] FieldID         ::=  IntConstant ':'

Field Requiredness

有两个明确的要求值,如果既没有给出要求也没有给出可选,则隐式应用第三个值:默认要求。

[17] FieldReq        ::=  'required' | 'optional' 

必要性的一般规则如下:

必选项

  • 写入:必填字段始终被写入并且预计会被设置。
  • 读取:必填字段始终被读取,并应包含在输入流中。
  • 默认值:始终写入

如果在读取期间缺少必填字段,则预期的行为是向调用者指示读取操作不成功,例如通过抛出异常或返回错误。

由于这种行为,必填字段极大地限制了与软版本控制相关的选项。因为它们必须在读取时出现,所以不能弃用这些字段。如果删除必填字段(或更改为可选字段),则数据在版本之间不再兼容。

可选项

  • 写入:可选字段仅在设置时写入
  • 读取:可选字段可能是也可能不是输入流的一部分。
  • 默认值:设置 isset 标志时写入

大多数语言实现使用所谓的“isset”标志的推荐做法来指示是否设置了特定的可选字段。仅写入设置了此标志的字段,相反,仅当从输入流中读取字段值时才设置标志。

默认 必选 (隐含)

  • 写:理论上,字段总是写的。该规则有一些例外情况,请参见下文。
  • 读取:与可选字段一样,该字段可能是也可能不是输入流的一部分。
  • 默认值:可能不写(见下一节)

默认要求是一个很好的起点。所需的行为是可选和必需的混合,因此内部名称为“选择加入,请求退出”。虽然理论上这些字段应该被写入(“req-out”),但实际上未设置的字段并不总是被写入。当字段包含一个值时尤其如此,根据定义,该值不能通过 thrift 传输。实现这一目标的唯一方法是根本不编写该字段,而这正是大多数语言所做的。

默认值的语义

关于该主题的讨论正在进行中,有关详细信息,请参阅 JIRA。并非所有实现都以完全相同的方式处理默认值,但当前的现状或多或少是默认字段通常在初始化时设置。因此,可能不会写入等于默认值的值,因为读取端将隐式设置该值。

另一方面,一个实现无论如何都可以自由写入默认值,因为没有硬性限制可以阻止这种情况。 这里要记住的要点是,任何未写入的默认值都会隐式成为接口版本的一部分。如果更改该默认值,则界面会发生变化。相反,如果将默认值写入输出数据,则 IDL 中的默认值可以随时更改,而不会影响序列化数据。

XSD 选项

N.B.:这些在 Facebook 有一些内部用途,但在 Thrift 目前没有任何用途。强烈建议不要使用这些选项。

[18] XsdFieldOptions ::=  'xsd_optional'? 'xsd_nillable'? XsdAttrs?

[19] XsdAttrs        ::=  'xsd_attrs' '{' Field* '}'

Functions

[20] Function        ::=  'oneway'? FunctionType Identifier '(' Field* ')' Throws? ListSeparator?

[21] FunctionType    ::=  FieldType | 'void'

[22] Throws          ::=  'throws' '(' Field* ')'

Types

[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

Constant Values

[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?)* '}'

Basic Definitions

Literal

[36] Literal         ::=  ('"' [^"]* '"') | ("'" [^']* "'")

Identifier

[37] Identifier      ::=  ( Letter | '_' ) ( Letter | Digit | '.' | '_' )*

[38] STIdentifier    ::=  ( Letter | '_' ) ( Letter | Digit | '.' | '_' | '-' )*

List Separator

[39] ListSeparator   ::=  ',' | ';'

Letters and Digits

[40] Letter          ::=  ['A'-'Z'] | ['a'-'z']

[41] Digit           ::=  ['0'-'9']

Reserved keywords

"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"