Header Propagation

Configure HTTP header propagation to subgraphs


You can configure which HTTP headers the GraphOS Router or Apollo Route Core includes in its requests to each of your subgraphs. You can define per-subgraph header rules, along with rules that apply to all subgraphs.

You define header rules in your YAML configuration file, like so:

YAML
router.yaml
1# ...other configuration...
2headers:
3  all: # Header rules for all subgraphs
4    request:
5      - propagate:
6          matching: ^upstream-header-.*
7      - remove:
8          named: "x-legacy-account-id"
9  subgraphs:
10    products: # Header rules for just the products subgraph
11      request:
12        - insert:
13            name: "router-subgraph-name"
14            value: "products"

Supported header rules

The router supports the following types of header rules:

propagate

Enables you to selectively pass along headers that were included in the client's request to the router.

You can specify which headers to propagate based on a matching regex pattern:

YAML
1- propagate:
2    matching: .*
note
The router never propagates so-called hop-by-hop headers (such as Content-Length) when propagating by pattern.

Alternatively, you can provide a static string via the named option. These named configurations have additional flexibility, because they support the following options:

  • default: A value to set if no value was sent by the client

  • rename: Renames the header's key to the provided value

YAML
1- propagate:
2    named: "x-user-id"
3    default: "abc123"
4    rename: "account-id"

remove

Enables you to selectively remove headers that were included in the client's request to the router. Like propagate, this option can match either a static string or a regular expression.

YAML
1# Do not send this subgraph the "Cookie" header.
2- remove:
3    named: "Cookie"
4- remove:
5    # Remove headers that include the legacy 'x-' prefix.
6    matching: ^x-.*$

insert

Enables you to add custom headers to requests going to a specific subgraph. These headers are strings (statics, coming from request body or from context) that originate in the router, instead of originating in the client.

  • Insert static header

YAML
1- insert:
2    name: "sent-from-our-apollo-router"
3    value: "indeed"
  • Insert header from context

YAML
1- insert:
2    name: "sent-from-our-apollo-router-context"
3    from_context: "my_key_in_context"
  • Insert header from request body

YAML
1- insert:
2    name: "sent-from-our-apollo-router-request-body"
3    path: ".operationName" # It's a JSON path query to fetch the operation name from request body
4    default: "UNKNOWN" # If no operationName has been specified

Example JSON path queries

Let's say you have a JSON request body with the following structure:

JSON
1{
2  "query": "{ products { id name } }",
3  "extensions": {
4    "metadata": [
5      {
6        "app_name": "random_app_name"
7      }
8    ]
9  }
10}

To fetch the value of the field app_name, the corresponding path is .extensions.metadata[0].app_name.

JSON path queries always begin with a period .

With this configuration:

YAML
1headers:
2  all:
3    request:
4      - insert:
5          name: from_app_name
6          path: .extensions.metadata[0].app_name

You will pass a header to all your subgraphs: "from_app_name": "random_app_name"

Rule ordering

Header rules are applied in the same order they're declared, and later rules can override the effects of earlier rules. Consider this example:

YAML
bad_configuration.yaml
1headers:
2  all:
3    request:
4      - remove:
5          named: "test"
6      - propagate:
7          matching: .*

In this example, first any header named test is removed from the list of headers to propagate. However, the list of headers to propagate is currently empty! Next, the propagate rule adds all headers to the propagation list, including test.

To correctly remove a header from the propagation list, make sure to define your remove rule after any propagate rules:

YAML
good_configuration.yaml
1headers:
2  all:
3    request:
4      - propagate:
5          matching: .*
6      - remove:
7          named: "test"

With this ordering, first all headers are added to the propagation list, then the test header is removed.

Example

Here's a complete example showing all the possible configuration options in use:

YAML
router.yaml
1headers:
2  # Header rules for all subgraphs
3  all:
4    request:
5      # Propagate matching headers
6      - propagate:
7          matching: ^upstream-header-.*
8      # Propagate matching headers
9      - propagate:
10          named: "some-header"
11          default: "default-value"
12          rename: "destination-header"
13      # Remove the "x-legacy-account-id" header
14      - remove:
15          named: "x-legacy-account-id"
16      # Remove matching headers
17      - remove:
18          matching: ^x-deprecated-.*
19      # Insert the 'my-company' header
20      - insert:
21          name: "my-company"
22          value: "acme"
23  # Subgraph-specific header rules
24  subgraphs:
25    products:
26      request:
27        # Calls to the products subgraph have the "router-subgraph-name" header set to `products`.
28        - insert:
29            name: "router-subgraph-name"
30            value: "products"
31    accounts:
32      request:
33        # Calls to the accounts subgraph have the "router-subgraph-name" header set to `accounts`.
34        - insert:
35            name: "router-subgraph-name"
36            value: "accounts"

Response header propagation

It is not currently possible to propagate response headers from subgraphs to clients using YAML configuration alone. However, you can achieve this using Rhai scripting.

This approach relies on the fact that each request has a context object that can store data for the duration of that request:

  1. For each subgraph response, copy header values into context.

  2. For the supergraph response, copy header values from the context onto the response.

Example router.yaml that will use the Rhai script:

YAML
router.yaml
1rhai:
2  main: "main.rhai"

Example Rhai script that collects set-cookie headers from subgraphs and merges them into a single client response header:

Rhai
./rhai/main.rhai
1fn supergraph_service(service) {
2  let add_cookies_to_response = |response| {
3    if response.context["set_cookie_headers"]?.len > 0 {
4      response.headers["set-cookie"] = response.context["set_cookie_headers"];
5    }
6  };
7
8  service.map_response(add_cookies_to_response);
9}
10
11fn subgraph_service(service, subgraph) {
12  let store_cookies_from_subgraphs = |response| {
13    if "set-cookie" in response.headers {
14      if response.context["set_cookie_headers"] == () {
15        response.context.set_cookie_headers = []
16      }
17
18      response.context.set_cookie_headers += response.headers.values("set-cookie");
19    }
20  };
21
22  service.map_response(store_cookies_from_subgraphs);
23}
note
If you require a configuration-based solution for response header propagation, please leave a comment on our issue tracker.

Propagation between subgraphs

It is not currently possible to propagate headers between subgraphs using YAML config alone. However, you can achieve this using Rhai scripting.

This approach relies on the fact that each request has a context object that can store data for the duration of that request:

  1. For each subgraph response, copy header values into context.

  2. For each subgraph request, copy header values from context into the subgraph request.

Example router.yaml that will use the Rhai script:

YAML
router.yaml
1rhai:
2  main: "main.rhai"

Example Rhai script that copies request-id and user headers:

Rhai
./rhai/main.rhai
1fn subgraph_service(service, subgraph) {
2    // The list of headers that you which to propagate.
3    let headers = ["request-id", "user"];
4
5    // Callback for subgraph requests. Inserts headers from context into the subgraph request.
6    let request_callback = |request| {
7        for key in headers {
8            if request.context[key] != () {
9                request.subgraph.headers[key] = request.context[key];
10            }
11        }
12    };
13
14    // Callback for subgraph responses. Pulls header values out of the response and inserts them into context.
15    let response_callback = |response| {
16        for key in headers {
17            if key in response.headers {
18                response.context[key] = response.headers[key];
19            }
20        }
21    };
22
23    // Register the callbacks.
24    service.map_request(request_callback);
25    service.map_response(response_callback);
26}
Feedback

Edit on GitHub

Forums