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/*}/**"
    }
  }
}

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=**}"
}
NB: 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.

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

  • 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.