Skip to content
This repository has been archived by the owner on Nov 6, 2020. It is now read-only.

Replace reqwest with hyper #8099

Merged
merged 16 commits into from
Mar 14, 2018
Merged

Replace reqwest with hyper #8099

merged 16 commits into from
Mar 14, 2018

Conversation

twittner
Copy link
Contributor

The approach taken is to spawn a background thread running a tokio Core and hyper Client, processing requests sent over a mpsc channel and send back responses over oneshot channels. Hyper client itself does not implement Send so this way we can keep the Send bound of the Fetch trait itself and make this a local change.

To accommodate existing synchronous uses of fetch responses an adapter BodyReader is provided which waits for response body Chunks to arrive.

Resolves #7829.

@parity-cla-bot
Copy link

It looks like @twittner hasn't signed our Contributor License Agreement, yet.

The purpose of a CLA is to ensure that the guardian of a project's outputs has the necessary ownership or grants of rights over all contributions to allow them to distribute under the chosen licence.
Wikipedia

You can read and sign our full Contributor License Agreement at the following URL: https://cla.parity.io

Once you've signed, plesae reply to this thread with [clabot:check] to prove it.

Many thanks,

Parity Technologies CLA Bot

@twittner twittner requested a review from tomusdrw March 13, 2018 11:37
@twittner
Copy link
Contributor Author

[clabot:check]

@parity-cla-bot
Copy link

It looks like @twittner signed our Contributor License Agreement. 👍

Many thanks,

Parity Technologies CLA Bot

@twittner twittner force-pushed the replace_reqwest_with_hyper branch from fef7c11 to dc8ed22 Compare March 13, 2018 15:17
Copy link
Contributor

@folsen folsen left a comment

Choose a reason for hiding this comment

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

LGTM other than the nitpicks. Although I really only browsed through the code for reasonable-ness, didn't dig too deep into all the logic around the read implementation and such.

break
}
} else {
let body = self.body.take().expect("within this loop `self.body` is always defined");
Copy link
Contributor

Choose a reason for hiding this comment

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

Tiny nitpick but we have a standardized format for these texts: https://wiki.parity.io/Coding-guide

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, I updated the message.

fn it_should_fetch() {
let client = Client::new().unwrap();
let abort = Abort::default().with_max_duration(Duration::from_secs(10));
let future = client.fetch("https://httpbin.org/drip?numbytes=3&duration=3&delay=1&code=200", abort);
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 no library to mock responses like this locally so we don't have to have an internet connection to run tests? (And potentially fail tests because httpbin.org is down or blocking us or something)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I know of mockito which can replace some of these tests. However it requires RUST_TEST_THREADS=1 and certain test cases like timeouts are curently not supported AFAICT.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Perhaps just spawn an http server in tests? Or at least bind a TcpSocket to a random port and respond with some predefined data.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I updated the tests to run a local test server instead.

Copy link
Collaborator

@tomusdrw tomusdrw left a comment

Choose a reason for hiding this comment

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

Looks good, couple of nitpicks as well.


fn fetch_with_abort(&self, url: &str, _abort: fetch::Abort) -> Self::Result {
fn fetch(&self, url: &str, abort: fetch::Abort) -> Self::Result {
let u = Url::parse(url).unwrap();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Perhaps Url crate should be re-exported from fetch if it's required to instantiate some other public types (Response)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, this may be convenient.

@@ -303,7 +307,7 @@ impl ContentFetcherHandler {
Ok(ValidatorResponse::Streaming(stream)) => {
trace!(target: "dapps", "Validation OK. Streaming response.");
let (reading, response) = stream.into_response();
fetch2.process_and_forget(reading);
pool.spawn(reading).forget();
Copy link
Collaborator

Choose a reason for hiding this comment

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

That could probably be pass-through (Since we use BodyParser to convert async->sync and later we use into_response() to convert from sync->async). To be investigated in subsequent PRs.

/// Create a new fetch client.
pub fn new() -> Result<Self, Error> {
let startup_done = Arc::new((Mutex::new(Ok(())), Condvar::new()));
let (tx_proto, rx_proto) = mpsc::channel(64);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Might be a bit too big for our use case, since every sender get's an additional slot anyway. Probably 16 would do.

impl Client {
/// Create a new fetch client.
pub fn new() -> Result<Self, Error> {
let startup_done = Arc::new((Mutex::new(Ok(())), Condvar::new()));
Copy link
Collaborator

Choose a reason for hiding this comment

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

I suppose std::sync::mpsc::channel/sync_channel() would be a bit simpler here, and also supports timeouts.

return Err(Error::Aborted)
}
if let Some(next_url) = redirect_location(url, &resp) {
if redirects >= abort.max_redirects() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is there a way to flatten the indentation here?

}
match try_ready!(self.body.poll()) {
None => Ok(Async::Ready(None)),
Copy link
Collaborator

Choose a reason for hiding this comment

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

Mixed tabs and spaces

while self.body.is_some() {
// Can we still read from the current chunk?
if self.offset < self.chunk.len() {
let k = cmp::min(self.chunk.len() - self.offset, buf.len() - m);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Might be good to use a bit more descriptive names for m and k

} else {
let body = self.body.take().expect("within this loop `self.body` is always defined");
match body.into_future().wait() { // wait for next chunk
Err((e, _)) => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

A bunch of more tabs and space mixture for identation.

self.offset += k;
m += k;
if m == buf.len() {
break
Copy link
Collaborator

Choose a reason for hiding this comment

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

I would put return Ok(m) here and instead of the other break - thanks to this we don't need the Ok(m) at the bottom

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think Ok(m) is still needed there, as the while loop might theoretically not be entered at all.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Oh, that's true. We could use loop and if let Some(body) = self.body.take() inside to avoid expect?

fn it_should_fetch() {
let client = Client::new().unwrap();
let abort = Abort::default().with_max_duration(Duration::from_secs(10));
let future = client.fetch("https://httpbin.org/drip?numbytes=3&duration=3&delay=1&code=200", abort);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Perhaps just spawn an http server in tests? Or at least bind a TcpSocket to a random port and respond with some predefined data.

@tomusdrw tomusdrw added A6-mustntgrumble 💦 Pull request has areas for improvement. The author need not address them before merging. M4-core ⛓ Core client code / Rust. labels Mar 13, 2018
@twittner twittner added the A0-pleasereview 🤓 Pull request needs code review. label Mar 14, 2018
self.offset += k;
m += k;
if m == buf.len() {
break
Copy link
Collaborator

Choose a reason for hiding this comment

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

Oh, that's true. We could use loop and if let Some(body) = self.body.take() inside to avoid expect?

@tomusdrw tomusdrw removed the A0-pleasereview 🤓 Pull request needs code review. label Mar 14, 2018
@folsen folsen merged commit 322dfbc into master Mar 14, 2018
@folsen folsen deleted the replace_reqwest_with_hyper branch March 14, 2018 12:41
@kirushik
Copy link
Collaborator

🎉

debris added a commit that referenced this pull request Mar 16, 2018
@5chdn 5chdn added this to the 1.11 milestone Mar 20, 2018
debris added a commit that referenced this pull request Apr 4, 2018
@5chdn 5chdn mentioned this pull request May 15, 2018
62 tasks
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
A6-mustntgrumble 💦 Pull request has areas for improvement. The author need not address them before merging. M4-core ⛓ Core client code / Rust.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants