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:

  1. a name of a field in the top-level of the request message
  2. a dot-separated path of field names leading to a field in a sub-message of the request message e.g. "book.author.name" where book is a message field in the request message, author is a message field in the book message, and name is a string field in the author 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.
  • 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 a path_template like foo/** 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.
    • 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}, where key is the name to be used in the key-value pair of the header
        • {key=template}, where the template is the segment(s) (expressed in this path_template syntax) to extract as the value paired with key
      • 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.
      • A variable segment must not contain other variable segments. This syntax is not recursive.
  • 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.