-
Notifications
You must be signed in to change notification settings - Fork 314
Pluggable HTTP client for CosmosDB #79
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
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great to see this work kicked off! ❤
@@ -0,0 +1,126 @@ | |||
use crate::errors::{AzureError, UnexpectedHTTPResult}; | |||
use async_trait::async_trait; | |||
use http::{Request, Response, StatusCode}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using these types somewhat binds us to the Hyper
ecosystem. I believe Surf
has a way to convert to and from these types though. Then again we probably don't want to implement our own versions of these types...
let body = String::from_utf8(request.body().to_vec())?; | ||
let hyper_request = hyper_request.body(Body::from(body))?; | ||
|
||
let hyper_response = self.request(hyper_request).await?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't hyper already return an http
response?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've tried but it seems to return an http::Response<hyper::Body>
. The trait expect an http::Response<Vec<u8>>
instead. In my understanding are basically the same thing (ie Body
implements From<Vec<u8>>
) so the move should be cheap:
error[E0308]: mismatched types
--> sdk/core/src/http_client.rs:83:12
|
83 | Ok(hyper_response)
| ^^^^^^^^^^^^^^ expected struct `Vec`, found struct `hyper::Body`
|
= note: expected struct `http::Response<Vec<u8>>`
found struct `http::Response<hyper::Body>`
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think in this case you can just use .map
to change the body type instead of constructing a whole new response object https://docs.rs/hyper/0.13.9/hyper/struct.Response.html#method.map
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a great idea!
I've tried it but unfortunately Hyper
's Body
is a stream so in order to obtain the Vec we must await on body::to_bytes()
.
Something like (this is as far as I went, it's not working):
Ok(hyper_response.map(async move |b| body::to_bytes(b).await?.to_vec()))
But:
- And async closures are unstable 🙅♂️ .
- We cannot use
?
inmap
.
Map creates a new type anyway so the effect is the same (albeit more verbose).
use std::marker::PhantomData; | ||
|
||
pub fn new<'a, IntoCowStr>( | ||
account: IntoCowStr, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, I think we should take String
instead of Cow<'a, str>
cosmos_uri_builder, | ||
}) | ||
} | ||
pub struct ClientBuilder<'a, CUB, HTTPClientToAssign> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really don't think the HttpClientToAssign
trick is worth it. Just make the http client required to construct the ClientBuilder
pub struct ClientBuilder<'a, CUB, HTTPClientToAssign> | ||
where | ||
CUB: CosmosUriBuilder, | ||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we create a ClientBuilder::new
function?
&self, | ||
) -> &hyper::Client<hyper_rustls::HttpsConnector<hyper::client::HttpConnector>> { | ||
self.user_client.hyper_client() | ||
fn http_client(&self) -> &dyn HttpClient { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if returning an Arc<Box<dyn HttpClient>>
is a more ergonimic thing to do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm going to merge this since we have a lot of changes coming for Cosmos. We can revisit this in a future PR to clean up.
failure = "0.1" | ||
async-trait = "0.1.36" | ||
oauth2 = { version = "4.0.0-alpha.2" } | ||
reqwest = "*" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's use a concrete version not "*"
Intro
This is a proposal for a pluggable HTTP client implementation.
I've started with CosmosDB because it's the most feature-complete create (and thus most complicated).
The first commit has:
HttpClient
definition (very simple).HttpClient
for bothReqwest
andHyper
.Get Database
function.Get Database
usage with the new pluggable interface.To support other HTTP clients all we need is to implement
HttpClient
for the new client. Basically it's one single function that translateshttp::request::Request
in the implementation-specific calls and maps back the result intohttp::response::Response
. The idea, as you can see, is to abstract away the HTTP call:As of now, the client instantiation goes through a builder that ensures a valid HTTP client has been specified:
The HTTP client must implement
HttpClient
interface (right now both reqwest and hyper are supported). TheHttpClient
trait isSend + Sync
so it's thread safe.The actual clients take the trait wrapped in a
Arc
heap allocated object:Box
is for ergonomics (so the trait object is owned).Arc
is to make sure the trait object is cloneable (plain trait objects are not cloneable). This may not be necessary, can be removed if needed at later time. This also allows to reuse the same underlying HTTP client in many Azure clients (good because the initialization is relatively expensive).This is an basic, working example:
Using reqwest
Using hyper
From now on the code is the behaves the same, as show from the execution result of
get_database
example:ToDo
Things to do before this PR can be merged:
Hyper
from the crate's dependencies (and move it to dev-dependencies as it will be used in the examples).TryFrom
implementations to accept a simplerhttp::response::Response
.Optional:
- [ ] ImplementHttpClient
for other HTTP libraries (surf maybe?) to check if the abstraction holds.