router
router copied to clipboard
YAML-based response headers configuration (e.g., subgraph to client, router originated)
Our current header propagation plugin is all about managing headers from client -> subgraph. Currently, we don't propagate headers from subgraph -> client, but there are users who require this functionality.
Problems we'd like to address in resolving this:
- How to resolve conflicts between subgraph responses?
- How to best represent in configuration yaml?
The use case for subgraph to client headers/cookies is often found in authentication. For example, when implementing OAuth2's standard you must send back a "state" to stop CSRFs securely to the client.
Right now I am forwarding the Set-Cookie header from subgraph to router response, intercepting cookies and changing them into headers when they return to the subgraph.
My use case doesn't have any level of conflict between microservices because all of the authentication with cookies happens in one service. If there was a way to just make that work, including cookie pass through, that would be great.
In the HTTP RFC we can have multiple headers with the same name, that could be an option from the configuration if users want to keep every duplicated headers. We could have something like merge_policy: duplicate
and merge_policy: merge
and if we merge we just handle conflicts depending the order we provide the configuration. If we same header propagation from different subgraph then we apply it in the same order and then the last one win. I think we already have the same behavior currently from client->subgraph.
This was marked as being split out of an api-1.0 issue, #980, but isn't marked with api-1.0. It probably should be...
I've done some investigation of the problem and figured out how to preserve headers from subgraph responses to the execution response. The tricky bit now is how to expose configuration functionality so that users can specify which header should be preserved and from which subgraphs such headers should be preserved. A good starting point would be to implement just the simple named propagation mechanism from the existing request
configuration model and only for subgraphs. We could then progressively extend this to include regex matching, insertion and removal if required and across all subgraphs if we find a useful use case.
The initial support would look like this:
headers:
request:
<all existing header configuration would be moved under request>
response:
subgraphs:
accounts:
- propagate:
named: "x-user-id"
default: "abc123"
We wouldn't provide support for rename initially. I'm not sure how useful that would be, but we could add it later if required.
When can we expect the fix for this?
@garypen please share updates and plan for this fix.
Since I know a lot of folks are following this issue, it's worth noting that we shipped documentation that exemplifies how to send headers from subgraphs to clients using Rhai script in a recent release. You can see that in our docs here.
To be clear: that doesn't mean this issue is closed/resolved, since we do want to make it possible to do this with YAML in the future. I am going to slightly modify the title of this issue though, in order to reflect that!
Possibly this is now a dup of #2444. #2444 is linked to in our docs.
Ah, good eye. Since this has a substantial number of emotes on it already and the original post still indicated a desire to solve this with YAML, I'm going to close #2444 as a duplicate of this and still keep this one open.
To get a fresher show of hands of votes, please don't hesitate to 👍 this comment if you're still looking for YAML and the Rhai based solution isn't sufficient for you!
We need to update the docs. Currently they point to #2444
Thanks for adding the example to the docs, @abernix. I'm getting the following error at runtime, using it (on Router v1.10.2):
2023-02-09T01:17:37.570234Z apollo-router/src/plugins/rhai.rs:1099 ERROR [trace_id=4d4b71459c4707098c46e94f50018582] apollo_router::plugins::rhai: map_response callback failed: Runtime error (line 17, position 26)
in closure call
I'm not too familiar with Rhai yet, but it seems like response.headers
is not an Object, and errors when a non-existent property is accessed.
@BrennanRoberts The error message is telling you that your rhai
code is failing at line 17, column 26
. If you can't sort this yourself, you can paste a snippet and we may be able to help.
response.headers
is a map of "headers". This is valid syntax for checking if a particular header is present in map:
if response.headers["set-cookie"] != () {
()
indicates that the "set-cookie" header is not present in response.headers
. If you just try to access
response.headers["set-cookie"]
and it isn't present, then you will get an exception.
@garypen, I'm using the recently posted script from Apollo's doc's, so I wanted to call out that it might have issues that others might run into as well.
Ah. I'll take a look at that when I have a few minutes.
Thanks for the callout. You might try the example in #2444, in the meantime.
This variation worked for me for sending multiple cookies back from a single subgraph, thanks to #2219:
let add_cookies_to_response = |response| {
if response.context["set_cookie_headers"]?.len > 0 {
response.headers["set-cookie"] = response.context["set_cookie_headers"];
}
};
service.map_response(add_cookies_to_response);
}
fn subgraph_service(service, subgraph) {
let store_cookies_from_subgraphs = |response| {
if response.headers.values("set-cookie")?.len > 0 {
if response.context["set_cookie_headers"] == () {
response.context.set_cookie_headers = []
}
response.context.set_cookie_headers += response.headers.values("set-cookie");
}
};
service.map_response(store_cookies_from_subgraphs);
}
Our use case for header forwarding are cache headers. We forward certain cache tags that are used in Varnish and extra response status headers, so we can skip caching a response when there is an error.
While the rhai script is a solution, it requires more work to define what headers should be forwarded, and since we have multiple projects running the same setup, we also have to maintain that script in multiple projects.
Adding to the list of folks that would love to see this rolled into configs instead of Rhai scripts.
Our use case is related to needing set-cookie
to be returned to maintain a particular behavior.
Having this set by config similar to request header propagation removes the need to maintain an additional script that could instead be defined similarly to how request headers are forwarded.