AIP-4222
Routing headers
In some situations, a gRPC API backend is able to route traffic more efficiently if it knows about some values in the request; however, the request payload can not be reasonably deconstructed on the wire to perform that routing.
Guidance
Code generators must use the annotations to generate client libraries that, on a per-RPC basis, extract routing information from the request payload and add that information to the routing header.
There are two annotations that specify how to extract routing information from the request payload:
- the
google.api.routing
annotation that specifies how to construct routing headers explicitly - the
google.api.http
annotation that may specify how to construct routing headers implicitly.
For any given RPC, if the explicit routing headers annotation is present, the code
generators must use it and ignore any routing headers that might be implicitly
specified in the google.api.http
annotation. If the explicit routing
headers annotation is absent, the code generators must parse the
google.api.http
annotation to see if it specifies routing headers
implicitly, and use that specification.
Explicit Routing Headers (google.api.routing
)
For an unary or server-streaming RPC the code generator must look at the routing
parameters specified in the google.api.routing
annotation, if present.
Any given routing parameter specifies a field name and a pattern with exactly one
named resource ID path segment. For example:
rpc CreateTopic(CreateTopicRequest) {
option (google.api.routing) = {
routing_parameters {
field: "parent"
path_template: "{project=projects/*}/**"
}
}
}
The value of the field field
must be one of the following:
- a name of a field in the top-level of the request message
- a dot-separated path of field names leading to a field in a sub-message of
the request message e.g.
"book.author.name"
wherebook
is a message field in the request message,author
is a message field in thebook
message, andname
is astring
field in theauthor
message
The actual field specified in the field field
must have the following
characteristics:
- it is type string
- it either has a path-like value format resembling a resource name or contains
an unstructured value that would be appropriate as an individual path segment
e.g. a project_id
.
Note: An empty google.api.routing
annotation is acceptable. It means that no
routing headers should be generated for the RPC, when they otherwise would be
e.g. implicitly from the google.api.http
annotation.
Note: It is acceptable to omit the pattern in the resource ID segment, {parent}
for example, is equivalent to {parent=*}
and must be parsed, e.g.:
routing_parameters {
field: "parent"
path_template: "projects/{parent}"
}
is the same as
routing_parameters {
field: "parent"
path_template: "projects/{parent=*}"
}
Note: It is acceptable to omit the path_template
field altogether. An omitted
path_template
is equivalent to a path_template
with the same resource ID name as
the field and the pattern **
, and must be parsed, e.g.:
routing_parameters {
field: "parent"
}
is the same as
routing_parameters {
field: "parent"
path_template: "{parent=**}"
}
Note: An omitted path_template
field does not indicate that key-value
pairs with empty values can be sent. It's merely a shorthand.
When the user supplies an instance of CreateTopicRequest
to the method, the
client library must match all the routing parameters in the order specified
to the fields of that instance. For each routing parameter, the pattern in the
path_template
must be matched to the input message field specified by the
routing parameter's field
field. In case of a match, the name of the resource ID
path segment must be used as a key, and the value of the resource ID path segment match
must be used as a value of a key-value pair to be appended to the x-goog-request-params
header.
Both the key and the value must be URL-encoded per RFC 6570 §3.2.2. This can be done with standard library URL encoding. For example, adding this header to a gRPC request in 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
In cases when multiple routing parameters have the same resource ID path segment name, thus referencing the same header key, the "last one wins" rule is used to determine which value to send. The "last" here is meant in terms of the order in which they're specified in the annotation. If some of the routing parameters with the same resource ID segment name have failed to match the field, or if the field was unset, or if the extracted matched value is an empty string, these parameters are not considered when determining which value to send.
Example:
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=**}"
}
}
In this case if in a given request the billing_project
field is set to an non-empty value,
its value will be sent with the project
key because the routing parameter looking at billing_project
field is specified last. If the billing_project
field is not set, the parent
field will be considered, first trying to send a
project with a subproject specified, and then without. Note that if a given request has a
parent
field with a value e.g. projects/100/subprojects/200/foo
, patterns in both first and second routing_parameters
will match it, but the second one will "win" since it is specified "last".
If all the routing parameters with the same resource ID segment name have failed to match the field, the key-value pair corresponding to those routing parameters' resource ID path segment name must not be sent.
If none of the routing parameters matched their respective fields, the routing header must not be sent.
Much like URL parameters, if there is more than one key-value pair to be sent, the &
character is used as the separator.
path_template
syntax
As seen in the above examples, the path_template
can use a variety of symbols
that are interpreted by code generators during conversion to regular expressions
or non-regular expression matcher implementations. The path_template
consists
of segments delimited by the segment delimiter. The syntax for path_template
is as follows:
- The only acceptable segment delimiter is
/
.- The last symbol in a
path_template
may be a delimiter - it will be ignored.
- The last symbol in a
- A segment must be of one of the following types:
*
: A single-segment wildcard. Corresponds to 1 or more non-/
symbols. The regex describing it is[^/]+
.- A Single-segment wildcard typically represents a resource ID.
**
: A multi-segment wildcard. Corresponds to 0 or more segments.- A multi-segment wildcard must only appear as the final segment or
make up the entire
path_template
. - In a multi-segment
path_template
, a multi-segment wildcard must appear immediately following a segment delimiter. This delimiter is consumed while matching so apath_template
likefoo/**
matches all of the following:foo
,foo/
,foo/bar/baz
. - In a multi-segment
path_template
, when used as the last segment the regex describing it is([:/].*)?
. - When used as the entire
path_template
, the regex describing it is.*
. - Segment delimiters are consumed while matching, including any preceding delimiter.
- A multi-segment wildcard must only appear as the final segment or
make up the entire
LITERAL
: A literal segment. A literal segment can contain any alphanumeric symbol.- A literal segment must not contain a symbol reserved in this syntax.
- Literal segments typically represent a resource collection ID or base path.
{}
: A variable segment. This matches part of the path as specified by its template.- A variable segment can be either of the following:
{key}
, wherekey
is the name to be used in the key-value pair of the header{key=template}
, where thetemplate
is the segment(s) (expressed in thispath_template
syntax) to extract as the value paired withkey
- A variable segment of just
{key}
defaults to a template of*
which matches 1 or more non-/
symbols.- While
{key=*}
is technically valid syntax, the simpler syntax of{key}
should be used.
- While
- A variable segment must not contain other variable segments. This syntax is not recursive.
- A variable segment can be either of the following:
- A segment must not represent a complex resource ID as described in AIP-4231. A Generator should emit an error in this case.
Implicit Routing Headers (google.api.http
)
Note: For an RPC annotated with the google.api.routing
annotation,
the google.api.http
annotation must be ignored for the purpose of adding
routing headers.
If an unary or server-streaming RPC is not annotated with the google.api.routing
annotation, code generators must look at URI-based variables declared in the
google.api.http
annotation and transcribe these into the
x-goog-request-params
header in unary calls. A URI-based variable is a
variable declared as a key in curly braces in the URI string. For example:
rpc CreateTopic(CreateTopicRequest) {
option (google.api.http).post = "{parent=projects/*}/topics";
}
Note: It is acceptable to omit the pattern in the resource ID segment, {parent}
for example, is equivalent to {parent=*}
and must be parsed.
In this case, the applicable variable is parent
, and it refers to the
parent
field in CreateTopicRequest
. When the user provides an instance of
CreateTopicRequest
to the method (or once the client library has built it, in
the case of method overloads), the client library must extract the key and
value, and append them to the x-goog-request-params
header. Both the key and
the value must be URL-encoded per RFC 6570 §3.2.2. This can be done
with standard library URL encoding. For example, adding this header to a gRPC
request in Go:
md := metadata.Pairs("x-goog-request-params",
url.QueryEscape("parent") + "=" + url.QueryEscape(req.GetParent()))
At runtime, if a field with the same name as the named parameter is unset on the request message, the key-value pair corresponding to that parameter must not be included in the routing header. If none of the parameters must be included in the routing header, the routing header must not be sent.
If the google.api.http
annotation contains additional_bindings
,
these patterns must be parsed for additional request parameters. Fields
not duplicated in the top-level (or additional_bindings
) pattern must
be included in request parameters, encoded in the same way.
Much like URL parameters, if there is more than one key-value pair, the &
character is used as the separator.
Changelog
- 2023-07-07: Include
path_template
syntax. - 2022-07-13: Updated to include the new
google.api.routing
annotation. - 2020-04-21: Explicitly parse path variables missing a trailing segment.
- 2019-11-27: Include
additional_bindings
as a request parameter source. - 2019-06-26: Fix wording and example of key-value pair encoding.
- 2019-06-20: Specify encoding of header parameters.