Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tower for use with http/graphql clients #2067

Merged
merged 25 commits into from
Sep 9, 2024
Merged

Conversation

dotdat
Copy link
Contributor

@dotdat dotdat commented Aug 21, 2024

This PR originally started by looking at how we might use clients (studio, graphql, http) that we could construct at the top of a command and then pass through, fully-formed to the place in which it is used.
However, during this discovery, I found a few requirements that needed to be addressed:

Any HTTP client created needs to respect the global --accept-invalid-hostnames and --accept-invalid-certs flags.
This is why we construct and re-use a reqwest::Client and reqwest::ClientBuilder in our config. When calling get_reqwest_client and get_reqwest_client_builder, this configuration is pre-filled.

HTTP/GraphQL clients aren't uniform throughout rover commands
There are a number of use cases that a client might perform:

  • download a binary
  • perform introspection
  • contact Studio
  • health-check the router
    Each one of these use cases may attach their own headers, timeouts, or retry logic, which is why we tend to construct our clients at the last possible moment.

reqwest supports a few nice things out of the box, such as proxies
There is a hazard in swapping out reqwest for something like hyper, as we are not entirely aware whether rover users are using any of these hidden features.

Given that, there are some improvements we could be make to our client ecosystem that makes the construction of these clients against their specific use cases more transparent / testable / modifiable in the future.

If we look at the use cases laid out currently, we can see that our client construction and use case has a few knobs that generally get tuned:

  • retries, whether to do so and also max elapsed time to retry
  • timeouts
  • header injection

With that, a tool like tower allows us to perform these types of tasks in a more declarative and composable way than passing around a ClientBuilder or manipulating headers ad hoc.

Initially, reqwest looked incompatible with tower based on its API. However, tower-reqwest seems to fill in that gap, by providing a way to wrap a client as a Service and apply tower layers to the client.

In this PR, I centralize http requests around http types and bytes for a robust body type.

In addition, GraphQL and Studio clients can be reduced to tower layers that perform their original tasks.
One note here is that the GraphQL layer omits internal retries and instead relies on an HttpService that provides retries (or not) as all retry logic is currently based on HTTP reasoning.

@dotdat dotdat force-pushed the dotdat/http-client-refactor branch 2 times, most recently from 07d3389 to b7f87eb Compare August 29, 2024 15:41
@dotdat dotdat marked this pull request as ready for review August 29, 2024 19:46
@dotdat dotdat requested a review from a team as a code owner August 29, 2024 19:46
@jonathanrainer jonathanrainer added this to the v0.27.0 milestone Aug 30, 2024
@dotdat dotdat force-pushed the dotdat/http-client-refactor branch from f4fd8c6 to 0f5de57 Compare September 3, 2024 14:10
Copy link
Contributor

@loshz loshz left a comment

Choose a reason for hiding this comment

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

Overall LGTM! Code is clean and very easy to follow. My only ask would be to add comments and tests to the rover-graphql and rover-http crates.

@@ -2,6 +2,7 @@ use std::collections::HashMap;

use crate::operations::graph::introspect::runner::graph_introspect_query;

#[cfg(test)]
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this supposed to be here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep. This is a little weird, but this is used only in tests. I had removed the whole thing previously as it wasn't used in the released binary, but then it ended up breaking the tests.

pub struct SubgraphIntrospectInput {
pub headers: HashMap<String, String>,
}
pub struct SubgraphIntrospectInput {}
Copy link
Contributor

Choose a reason for hiding this comment

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

Could this just be a unit struct?

Suggested change
pub struct SubgraphIntrospectInput {}
pub struct SubgraphIntrospectInput;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This could be, but usually I defer to using an empty struct unless it's specifically a marker / empty by design. In this case, we're just not expecting anything as input, but could be expanded to require something later.
All said and done, they're exactly the same in practice it's just a personal preference here.

.map(|result| match result {
Ok(data) => Q::map_response(data),
Err(err) => {
if err.to_string().contains("Cannot query field") {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there any way to assert a concrete type here as opposed to a strcmp? Or do we not own this error?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This comes from the errors field in a GraphQL response, so the error is a little-bit free form and produced by the subgraph's server implementation. There is probably a way to be more specific about this since we kinda know that the error case exists, but I left it alone here mostly because I think it belongs to a separate type of PR to classify that type of error.

Copy link
Contributor

@aaronArinder aaronArinder left a comment

Choose a reason for hiding this comment

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

in favor! approving to clear the way; though, tests look like they might be failing (not sure if it's ephemeral--haven't looked) and we might want to figure out whether this should be its own release or what makes sense for it to ship with

@aaronArinder aaronArinder force-pushed the dotdat/http-client-refactor branch from 0f5de57 to c61e7c6 Compare September 3, 2024 18:54
@dotdat dotdat force-pushed the dotdat/http-client-refactor branch 3 times, most recently from 7ccc5c6 to 8b5d98c Compare September 5, 2024 17:33
@dotdat dotdat changed the title Use tower for http/graphql clients Add tower for use with http/graphql clients Sep 5, 2024
@dotdat dotdat force-pushed the dotdat/http-client-refactor branch from 5d7cefe to a12fe25 Compare September 6, 2024 00:35
@dotdat dotdat force-pushed the dotdat/http-client-refactor branch from 8abe179 to e5445cc Compare September 9, 2024 14:59
@dotdat
Copy link
Contributor Author

dotdat commented Sep 9, 2024

I have removed the touchpoints for this with the existing rover crates (rover, rover-client, sputnik) as to provide the tooling outright, but to be more cautious about how it is implemented through the rover stack.

@dotdat dotdat enabled auto-merge (squash) September 9, 2024 15:24
@dotdat dotdat merged commit e3bd09a into main Sep 9, 2024
22 checks passed
@dotdat dotdat deleted the dotdat/http-client-refactor branch September 9, 2024 15:27
@jonathanrainer jonathanrainer modified the milestones: v0.27.0, v0.26.2 Sep 10, 2024
jonathanrainer added a commit that referenced this pull request Sep 10, 2024
## 🐛 Fixes

- **Avoid misleading warning when `--output` is not specified - @glasser
#2100**

In the release of v0.26.1 logic was added to disable the output flag if
the Federation version was less than 2.9, however this was being printed
even when the `--output` flag was not supplied. This has been corrected.

- **Improve `--graph-ref` option - @glasser #2101**

In the release of v0.26.0 the `--graph-ref` option was added to
`supergraph compose` as well as `rover dev`. However, the behaviour when
`--graph-ref` was used in conjunction with `--config` did not work as
documented. This is now fixed. Furthermore, both `rover dev` and
`supergraph compose`, when using only the `--graph-ref` option, respect
the graph ref's Federation version.

- **Further improve `--graph-ref` option - @glasser #2105**

Improves on the above by fixing some corner cases that prevented #2101
from working as intended

## 🛠 Maintenance

- **Update `eslint` to v9.10.0 - @jonathanrainer #2106**
- **Update `concurrently` to v9.0.0 - @jonathanrainer #2108**
- **Update `manylinux` CI Docker Image to v2024.09.09 - @jonathanrainer
#2110**
- **Update Rust to v1.81.0 - @jonathanrainer #2107**
- **Pass GitHub Tag to GitHub Actions Workflow - @glasser #2109**
- **Add `tower` for use with HTTP/GraphQL clients - @dotdat #2067**

## 📚 Documentation

- **Fix Glossary links - @Meschreiber @pnodet #2114**
aaronArinder pushed a commit that referenced this pull request Sep 10, 2024
- **Avoid misleading warning when `--output` is not specified - @glasser

In the release of v0.26.1 logic was added to disable the output flag if
the Federation version was less than 2.9, however this was being printed
even when the `--output` flag was not supplied. This has been corrected.

- **Improve `--graph-ref` option - @glasser #2101**

In the release of v0.26.0 the `--graph-ref` option was added to
`supergraph compose` as well as `rover dev`. However, the behaviour when
`--graph-ref` was used in conjunction with `--config` did not work as
documented. This is now fixed. Furthermore, both `rover dev` and
`supergraph compose`, when using only the `--graph-ref` option, respect
the graph ref's Federation version.

- **Further improve `--graph-ref` option - @glasser #2105**

Improves on the above by fixing some corner cases that prevented #2101
from working as intended

- **Update `eslint` to v9.10.0 - @jonathanrainer #2106**
- **Update `concurrently` to v9.0.0 - @jonathanrainer #2108**
- **Update `manylinux` CI Docker Image to v2024.09.09 - @jonathanrainer
- **Update Rust to v1.81.0 - @jonathanrainer #2107**
- **Pass GitHub Tag to GitHub Actions Workflow - @glasser #2109**
- **Add `tower` for use with HTTP/GraphQL clients - @dotdat #2067**

- **Fix Glossary links - @Meschreiber @pnodet #2114**
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants