Skip to content

Conversation

ardatan
Copy link
Member

@ardatan ardatan commented Oct 6, 2025

Fixes #461
Ref ROUTER-149

This PR implements the feature to override existing subgraph urls defined in the supergraph;
Cosmo -> https://cosmo-docs.wundergraph.com/router/proxy-capabilities/override-subgraph-config
Apollo Router -> https://www.apollographql.com/docs/graphos/routing/configuration/yaml#override_subgraph_url

Configuration;

override_subgraph_urls:
  subgraphs:
     products: 
          # Static definition
         url: "http://static-def.com"
         # Dynamic header based definition
         expression: |
            if .request.headers."x-region" == "us-east" {
                "http://www.example-us-east.com/graphql"
            } else if .request.headers."x-region" == "eu-west" {
                "http://www.example-eu-west.com/graphql"
            } else {
                "http://example.com/graphql"
            }

Copy link

github-actions bot commented Oct 6, 2025

k6-benchmark results

     ✓ response code was 200
     ✓ no graphql errors
     ✓ valid response structure

     █ setup

     checks.........................: 100.00% ✓ 231915      ✗ 0    
     data_received..................: 6.8 GB  226 MB/s
     data_sent......................: 91 MB   3.0 MB/s
     http_req_blocked...............: avg=3.57µs   min=682ns   med=1.74µs  max=10.78ms  p(90)=2.44µs  p(95)=2.78µs  
     http_req_connecting............: avg=616ns    min=0s      med=0s      max=3.32ms   p(90)=0s      p(95)=0s      
     http_req_duration..............: avg=18.93ms  min=1.86ms  med=17.98ms max=110.03ms p(90)=26.19ms p(95)=29.28ms 
       { expected_response:true }...: avg=18.93ms  min=1.86ms  med=17.98ms max=110.03ms p(90)=26.19ms p(95)=29.28ms 
     http_req_failed................: 0.00%   ✓ 0           ✗ 77325
     http_req_receiving.............: avg=132.15µs min=23.86µs med=38.64µs max=71.38ms  p(90)=89.38µs p(95)=366.65µs
     http_req_sending...............: avg=23.5µs   min=5.34µs  med=10.34µs max=21.43ms  p(90)=15.76µs p(95)=28.11µs 
     http_req_tls_handshaking.......: avg=0s       min=0s      med=0s      max=0s       p(90)=0s      p(95)=0s      
     http_req_waiting...............: avg=18.77ms  min=1.81ms  med=17.86ms max=80.88ms  p(90)=25.94ms p(95)=29ms    
     http_reqs......................: 77325   2572.414935/s
     iteration_duration.............: avg=19.39ms  min=4.51ms  med=18.33ms max=228.15ms p(90)=26.6ms  p(95)=29.78ms 
     iterations.....................: 77305   2571.749583/s
     vus............................: 50      min=50        max=50 
     vus_max........................: 50      min=50        max=50 

Copy link

github-actions bot commented Oct 6, 2025

🐋 This PR was built and pushed to the following Docker images:

Image Names: ghcr.io/graphql-hive/router

Platforms: linux/amd64,linux/arm64

Image Tags: ghcr.io/graphql-hive/router:pr-471 ghcr.io/graphql-hive/router:sha-9a7348d

Docker metadata
{
"buildx.build.ref": "builder-483316d8-e78c-44a2-93b5-669183dd8476/builder-483316d8-e78c-44a2-93b5-669183dd84760/qxt5m3ibfgq6oekhw4vzwu52j",
"containerimage.descriptor": {
  "mediaType": "application/vnd.oci.image.index.v1+json",
  "digest": "sha256:91dc9853abc3bb6a222ce969f885b9e6096d77070f86abbbcec024b4c7c93f96",
  "size": 1609
},
"containerimage.digest": "sha256:91dc9853abc3bb6a222ce969f885b9e6096d77070f86abbbcec024b4c7c93f96",
"image.name": "ghcr.io/graphql-hive/router:pr-471,ghcr.io/graphql-hive/router:sha-9a7348d"
}

@ardatan ardatan marked this pull request as ready for review October 7, 2025 12:52
@ardatan ardatan requested review from dotansimha and kamilkisiela and removed request for dotansimha October 7, 2025 12:52
@ardatan
Copy link
Member Author

ardatan commented Oct 7, 2025

@gemini-code-assist review and summary

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a powerful feature to override subgraph URLs, either statically or dynamically using VRL expressions. The implementation is well-structured, introducing a new ExpressionHTTPExecutor to handle dynamic resolution and cleanly integrating it with the existing HTTPSubgraphExecutor. The configuration is defined in a new override_subgraph_urls.rs file and integrated into the main router config. The changes also include necessary refactoring in the executor map and semaphore handling to support this new functionality. The documentation has been updated to reflect the new feature.

I've found a critical issue that will prevent compilation due to a missing From trait implementation, and a medium-severity issue in the documentation that should be addressed to make it complete.

@starJammer
Copy link

In the above configuration example, it appears that only the headers approach is supported, yes?

In other words, the JWT token claims can't be used, correct?

This should work for my use case, since I'm passing in a REGION header. The original reason I used the header is because the Apollo router only supports JWT decoding with the enterprise license. Otherwise, I would have used the JWT directly.

In reality, the header was a workaround.
However, our API gateway was already decoding the JWT itself and could add the region header before forwarding to the graph router.

@ardatan
Copy link
Member Author

ardatan commented Oct 9, 2025

JWT Plugin is still WIP --> #455
After it gets merged, it will be possible to access jwt payload inside the VRL expression. It is not limited to headers, VRL can access all request details, the request context including JWT(after it's implemented).

products:
expression: |2-
if .request.headers."x-region" == "us-east" {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a case sensitive matching that is occurring? Or is it case-insensitive?
Are there any other request attributes that can be used here? Looks like the entire request object is available for use.

Copy link

@starJammer starJammer Oct 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additionally, does the statement have to return a value here?
Or will the default automatically be reused if no value is returned in the expression?

For example, suppose my default url is http://hello.us.com.

Is this a valid configuration for the products subgraph?:

override_subgraph_urls:
  subgraphs:
    products:
      expression: |2-
                if .request.headers."x-region" == "eu-east" {
                    "https://hello.eu.eu/graphql"
                }

I want to point out TWO things here:

  1. Notice that the default is http (plain http) and the EU version of the URL is https (secure). Is this sort of change to the scheme supported? This was not originally supported in the Apollo router and but I submitted a PR for it to be supported here Feature/6897 - Allow scheme for subgraph to be inspected and updated apollographql/router#6906

Since Apollo didn't support it by default, I don't want to assume that it is supported here.

  1. Notice that I didn't include an else statement in the expression in the configuration above. I would expect that the default URL would be used in this case. Is this supported, or does the expression have to return a value, even the default value, or will the default value be used if no value is returned?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My Rust isn't good at all so I can't quite look at the code myself to answer the above questions.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Notice that I didn't include an else statement in the expression in the configuration above. I would expect that the default URL would be used in this case. Is this supported, or does the expression have to return a value, even the default value, or will the default value be used if no value is returned?

Yes the URL in the supergraph will be used if the expression doesn't return anything.

Notice that the default is http (plain http) and the EU version of the URL is https (secure). Is this sort of change to the scheme supported? This was not originally supported in the Apollo router and but I submitted a PR for it to be supported here

Scheme doesn't matter in our implementation currently. It is possible to switch in between http and https.

Copy link
Member Author

@ardatan ardatan Oct 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a case sensitive matching that is occurring? Or is it case-insensitive?

Header names are always lower-cased actually. So they are case-insensitive.

Are there any other request attributes that can be used here? Looks like the entire request object is available for use.

It has details like URL(search params etc), headers, method and the original router operation. After the implementation of JWT, it will have the jwt payload there too. Once we have a proper documentation then we'll explain the available variables in the VRL Context!

pub struct ClientRequestDetails<'a> {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds great! This is perfect. Because my organization chose to NOT use Apollo enterprise, we're only using basic query features of the router so switching to this new router now should be super easy.

The only thing we'll want to wait for is the ability to tie it to the Hive Registry for the supergraph schema. I saw there is an issue open for that and that it's already on the timeline.

@ardatan ardatan changed the title Override Subgraph URLs feat(router): Override Subgraph URLs Oct 13, 2025
@kamilkisiela kamilkisiela force-pushed the override-subgraph-urls branch from 3b7a28c to 5435ea1 Compare October 15, 2025 08:38
Copy link
Contributor

@kamilkisiela kamilkisiela left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see we have a new subgraph executor, but I don't think we should.
The executor_map is a hashmap where keys are endpoints, and I think that's why you did it this way, but it could be changed into subgraph's name instead and then the endpoint would be dynamic.

This way we just add resolve_endpoint to existing executor and move on.

About expression returning nothing and falling back to original value. I don't like this, it could be more explicit and done with returning .original_url value or something like that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Subgraph URL Rewriting Support - Multi-region graphql routing

3 participants