Hyper: Ergonomics of async Client handles

Created on 13 Jan 2017  路  7Comments  路  Source: hyperium/hyper

On the tokio branch of Hyper right now a Client, the way to issue HTTP requests, is created with a Handle from the tokio-core crate. While simple it's not necessarily easy to always get your hands on a Handle as it requires you to manage the event loop yourself, such as shown in the example.

I'm wondering if we can perhaps have a more ergonomic option available for those that don't already have a Handle in their hands? This is sort of a general question for all async client libraries built on tokio, and I think it's also something where once we have a solution we may want to canonicalize it in one of the core libraries or in a separate core crate.

Some ideas I have could be:

  1. A second constructor could be provided, Client::easy() (or similarly named). This creates a client which internally references a remote event loop managed by hyper itself.
  2. A entirely synchronous client could be provided under a whole new type. This synchronous client would internally contain a Core that it would use to perform all operations.

I'm not entirely sure how (2) would work with streaming bodies, but it would perhaps provide the ergonomic entry point one might expect from an HTTP library.

@carllerche @aturon, thoughts on this? Or just on the general topic of the ergonomics of async clients?

A-client

Most helpful comment

I'm wondering about sort of the opposite of this. Right now hyper's Client requires a tokio_core::reactor::Handle. It's not clear from the current docs why this isn't generic over all implementations of futures::future::Executor.

In the example in the new guides, we see the client being constructed with a handle, but then the future still needs to be executed by calling core.run(work) at the bottom. It's not clear from the example why the client needed a reference to the core's handle if the future is going to be passed to the core to execute.

My use case is in designing an API client library that uses hyper underneath. It doesn't feel right to require that users of my library provide a tokio reactor in order to construct a client. Ideally the client would be agnostic to the executor, letting the user decide how the application should do it (synchronous via Future::wait, thread pool, tokio reactor, etc.)

All 7 comments

Another option is something like https://github.com/tailhook/tk-easyloop where it becomes easy to acquire a global handle.

A Client::easy() would be nice to remove the need for users to handle a core, but the other question is what to do with the futures of requests.

let client = Client::easy();
let work = client.get(url).and_then(dump_body_to_stdout);
// what does a  user do with work

I suppose if user wanted to build up a combination of requests, they could then wait on a join of all of them, such as work.wait(), and the Core in another thread should just keep making progress, right?

Perhaps the easy client also has the idea of being eager: you build up a unit of work, and then give it to the easy client and let it run in an offthread.

client.eager(work); // or spawn(work) or similar

I'm wondering about sort of the opposite of this. Right now hyper's Client requires a tokio_core::reactor::Handle. It's not clear from the current docs why this isn't generic over all implementations of futures::future::Executor.

In the example in the new guides, we see the client being constructed with a handle, but then the future still needs to be executed by calling core.run(work) at the bottom. It's not clear from the example why the client needed a reference to the core's handle if the future is going to be passed to the core to execute.

My use case is in designing an API client library that uses hyper underneath. It doesn't feel right to require that users of my library provide a tokio reactor in order to construct a client. Ideally the client would be agnostic to the executor, letting the user decide how the application should do it (synchronous via Future::wait, thread pool, tokio reactor, etc.)

When using the default connector, it's needed to construct TcpStreams. But besides that, it's needed to use tokio-protos BindClient. Looks like this is a first step: https://github.com/tokio-rs/tokio-proto/pull/172

I'm in the same position as @jimmycuadra in that I'm working with an API client library that wraps Hyper. Maybe I'm missing something here, but it seems that being reactor-agnostic is not possible for client libraries using Hyper's 0.11.x series, but if the hyper::Client were to accept a Remote parameter instead of a Handle parameter, then it would be possible for the API client library to hide Core because then the library could manage its own background thread in which it runs a private Core reactor. As it is, I don't see how it's possible for the reactor to run in a different thread from the thread that constructs the Client.

Someone please correct me if I'm wrong.

If Client::new takes a &Executor<...> instead of Handle a first issue is that either it has to take a Arc<Executor<...>> actually (which might not be the best ergonmics wise either), or a lifetime has to be added to Client, which might not be a very popular choice.

I'm slightly leading towards Arc<Executor<...>> but maybe there is some opinion? Because the life time feels like the right way to do it, but again it can be annoying for the API user.

The new client API for 0.12 is simply Client::new(), which will integrate with the default tokio reactor/executor.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

DiamondLovesYou picture DiamondLovesYou  路  19Comments

bagder picture bagder  路  32Comments

orangesoup picture orangesoup  路  19Comments

timonv picture timonv  路  24Comments

DemiMarie picture DemiMarie  路  25Comments