AIP-4222
路由头
在某些情况下,如果gRPC API后端知道请求中的某些值,它能够更有效地路由流量;然而,请求负载无法在传输过程中合理解析以执行该路由。
指南
代码生成器**必须**使用注释来生成客户端库,这些库在每个RPC的基础上从请求负载中提取路由信息,并将该信息添加到路由头中。
有两个注释指定如何从请求负载中提取路由信息:
- routing注释,明确指定如何构建路由头
- http注释,可能隐式指定如何构建路由头
对于任何给定的RPC,如果存在明确的路由头注释,代码生成器**必须**使用它,并忽略可能在http注释中隐式指定的任何路由头。如果明确的路由头注释不存在,代码生成器**必须**解析http注释,查看是否隐式指定了路由头,并使用该规范。
明确的路由头(google.api.routing)
对于一元或服务器流式RPC,代码生成器**必须**查看routing注释中指定的路由参数(如果存在)。任何给定的路由参数指定一个字段名称和一个模式,该模式恰好包含一个命名的资源ID路径段。例如:
```proto rpc CreateTopic(CreateTopicRequest) { option (google.api.routing) = { routing_parameters { field: “parent” path_template: “{project=projects/*}/**” } } } ```
字段`field`的值**必须**是以下之一:
- 请求消息顶层字段的名称
- 指向请求消息子消息中字段的点分隔路径,例如`“book.author.name”`,其中`book`是请求消息中的消息字段,`author`是`book`消息中的消息字段,`name`是`author`消息中的`string`字段
字段`field`中指定的_实际字段_**必须**具有以下特征:
- 它是`string`类型
- 它要么具有类似于资源名称的路径值格式,要么包含适合作为单个路径段的非结构化值,例如`project_id`。
注意: 空的`google.api.routing`注释是可以接受的。这意味着不应为该RPC生成路由头,即使它们可能从`google.api.http`注释中隐式生成。
注意: 可以省略资源ID段中的模式,例如`{parent}`等同于`{parent=*}`,并且**必须**被解析,例如: ```proto routing_parameters { field: “parent” path_template: “projects/{parent}” } ```
等同于
```proto routing_parameters { field: “parent” path_template: “projects/{parent=*}” } ```
注意: 可以完全省略`path_template`字段。省略的`path_template`等同于具有与字段相同的资源ID名称和模式`**`的`path_template`,并且**必须**被解析,例如: ```proto routing_parameters { field: “parent” } ```
等同于
```proto routing_parameters { field: “parent” path_template: “{parent=**}” } ```
注意: 省略的`path_template`字段并不表示可以发送具有空值的键值对。它只是一个简写。
当用户向方法提供`CreateTopicRequest`的实例时,客户端库**必须**按照指定的顺序将所有路由参数与该实例的字段匹配。对于每个路由参数,`path_template`中的模式**必须**与路由参数的`field`字段指定的输入消息字段匹配。如果匹配,资源ID路径段的名称必须用作键,资源ID路径段匹配的值必须用作键值对的值,附加到`x-goog-request-params`头中。
键和值**必须**按照[RFC 6570 §3.2.2][]进行URL编码。这可以使用标准库的URL编码完成。例如,在Ruby中将此头添加到gRPC请求中:
```ruby header_params = {} if (pattern_matches("{project=projects/*}/**", request.parent)) header_params[“project”] = extract_match_value("{project=projects/*}/**", request.parent) end request_params_header = URI.encode_www_form header_params metadata[:“x-goog-request-params”] = request_params_header ```
在多个路由参数具有相同资源ID路径段名称的情况下,引用相同的头键,使用“最后一个胜出”规则来确定发送哪个值。“最后一个”是指在注释中指定的顺序。如果某些具有相同资源ID段名称的路由参数未能匹配字段,或者字段未设置,或者提取的匹配值为空字符串,则在确定发送哪个值时不考虑这些参数。
示例:
```proto option (google.api.routing) = { routing_parameters { field: “parent” path_template: “{project=projects/*}/**” } routing_parameters { field: “parent” path_template: “{project=projects/*/subprojects/*}/**” } routing_parameters { field: “billing_project” path_template: “{project=**}” } } ```
在这种情况下,如果在给定请求中`billing_project`字段设置为非空值,则其值将与`project`键一起发送,因为查看`billing_project`字段的路由参数是最后指定的。如果`billing_project`字段未设置,则将考虑`parent`字段,首先尝试发送指定子项目的项目,然后发送未指定子项目的项目。请注意,如果给定请求的`parent`字段具有值,例如`projects/100/subprojects/200/foo`,则第一个和第二个`routing_parameters`中的模式都将匹配它,但第二个将“胜出”,因为它是“最后”指定的。
如果所有具有相同资源ID段名称的路由参数都未能匹配字段,则**不得**发送与这些路由参数的资源ID路径段名称对应的键值对。
如果没有任何路由参数匹配其各自的字段,则**不得**发送路由头。
与URL参数类似,如果有多个键值对要发送,则使用`&`字符作为分隔符。
`path_template`语法
如上例所示,`path_template`可以使用各种符号,这些符号在转换为正则表达式或非正则表达式匹配器实现时由代码生成器解释。`path_template`由段分隔符分隔的段组成。`path_template`的语法如下:
- 唯一可接受的段分隔符是`/`。
- `path_template`中的最后一个符号**可以**是分隔符 - 它将被忽略。
- 段**必须**是以下类型之一:
- `*`:单段通配符。对应于1个或多个非`/`符号。描述它的正则表达式是`[^/]+`。
- 单段通配符通常表示资源ID。
- `**`:多段通配符。对应于0个或多个段。
- 多段通配符**必须**仅作为最终段出现或构成整个`path_template`。
- 在多段`path_template`中,多段通配符**必须**紧跟在段分隔符之后出现。此分隔符在匹配时被消耗,因此像`foo/**`这样的`path_template`匹配以下所有内容:`foo`,`foo/`,`foo/bar/baz`。
- 在多段`path_template`中,当用作最后一段时,描述它的正则表达式是`([:/].*)?`。
- 当用作整个`path_template`时,描述它的正则表达式是`.*`。
- 段分隔符在匹配时被消耗,包括任何前面的分隔符。
- `LITERAL`:文字段。文字段可以包含任何字母数字符号。
- 文字段**不得**包含此语法中保留的符号。
- 文字段通常表示资源集合ID或基本路径。
- `{}`:变量段。这匹配由其模板指定的路径部分。
- 变量段可以是以下之一:
- `{key}`,其中`key`是用于头键值对的名称
- `{key=template}`,其中`template`是要提取为与`key`配对的值的段(以`path_template`语法表示)
- 仅`{key}`的变量段默认为`*`模板,匹配1个或多个非`/`符号。
- 虽然`{key=*}`在技术上是有效的语法,但**应**使用更简单的`{key}`语法。
- 变量段**不得**包含其他变量段。此语法不是递归的。
- 变量段可以是以下之一:
- `*`:单段通配符。对应于1个或多个非`/`符号。描述它的正则表达式是`[^/]+`。
- 段**不得**表示[AIP-4231][]中描述的复杂资源ID。生成器**应**在这种情况下发出错误。
隐式路由头(google.api.http)
注意: 对于使用routing注释注释的RPC,必须忽略http注释以添加路由头。
如果一元或服务器流式RPC未使用routing注释注释,代码生成器**必须**查看http注释中声明的基于URI的变量,并将这些变量转录到一元调用中的`x-goog-request-params`头中。基于URI的变量是在URI字符串中作为大括号中的键声明的变量。例如:
```proto rpc CreateTopic(CreateTopicRequest) { option (google.api.http).post = “{parent=projects/*}/topics”; } ```
注意: 可以省略资源ID段中的模式,例如`{parent}`等同于`{parent=*}`,并且**必须**被解析。
在这种情况下,适用的变量是`parent`,它引用`CreateTopicRequest`中的`parent`字段。当用户向方法提供`CreateTopicRequest`的实例时(或者在方法重载的情况下,客户端库构建它之后),客户端库必须提取键和值,并将它们附加到`x-goog-request-params`头中。键和值**必须**按照[RFC 6570 §3.2.2][rfc 6570 §3.2.2]进行URL编码。这可以使用标准库的URL编码完成。例如,在Go中将此头添加到gRPC请求中:
```go md := metadata.Pairs(“x-goog-request-params”, url.QueryEscape(“parent”) + “=” + url.QueryEscape(req.GetParent())) ```
在运行时,如果请求消息上未设置与命名参数同名的字段,则**不得**在路由头中包含与该参数对应的键值对。如果没有任何参数必须包含在路由头中,则**不得**发送路由头。
如果http注释包含`additional_bindings`,则**必须**解析这些模式以获取额外的请求参数。未在顶级(或`additional_bindings`)模式中重复的字段**必须**包含在请求参数中,并以相同的方式编码。
与URL参数类似,如果有多个键值对,则使用`&`字符作为分隔符。
变更日志
- **2023-07-07**:包含`path_template`语法。
- **2022-07-13**:更新以包含新的`google.api.routing`注释。
- **2020-04-21**:明确解析缺少尾随段的路径变量。
- **2019-11-27**:包含`additional_bindings`作为请求参数源。
- **2019-06-26**:修复键值对编码的措辞和示例。
- **2019-06-20**:指定头参数的编码。