I'm writing a client for a type of API-based HTTP service. Because the API crate is useful as a library crate and HTTP is used trivially I'd like to not pick the HTTP implementation myself, though so far I'll be using hyper in the top-level program. I'd also like the HTTP client to be required as late as possible in the build for better build parallelism.
hyper::Client::request is the only method I really need from an http client, and I can easily keep an hyper::Client in my ApiClient struct.
If I try to switch to the Tower Service trait, which seems like a sufficiently generic abstraction that has no dependencies, my ApiClient<S: tower_service::Service> is forced to take &mut self on almost all methods, despite keeping no relevant state. This is a poor abstraction because my API is stateless, the HTTP layer also is, and my API becomes much harder to use.
I might still be able to work with tower by having ApiClient require an S: Clone bound as well and implementing Service on &hyper::Client (references are cloned for free), but at this point the traits are working against me.
What is the simplest abstraction I could use for HTTP? Something like Fn(Request) -> ResponseFuture? Is there something like that already defined?
Implementing tower::Service for a shared reference type seems reasonable to me (and it follows a pattern that appears even in std, like impl<'a> Read for &'a TcpStream). I don't understand what you have in mind here:
Why would Clone necessarily be involved with this approach?
struct ApiClient<S: Service + Clone> {
http: S,
}
impl<S: Service + Clone> ApiClient {
pub async fn frobs(&self) {
let mut http = self.http.clone();
http.call(http::Request::get("http://example.com")).await
}
}
Now frobs doesn't need &mut self, but it's not great, because you wouldn't want to actually use it on a random Service impl, only one that is trivial to clone.
let client = hyper::Client::new();
let http = http_fn(move |r| client.request(r));
It can't wrap a tower_service::Service without interior mutability or cloning shenanigans, but it seems Tower support is less practical than the rest of the http ecosystem.