async-h1 icon indicating copy to clipboard operation
async-h1 copied to clipboard

Tokio server/client example?

Open bspeice opened this issue 4 years ago • 2 comments

Question up front: is it possible to use this crate with tokio?

I'm interested in building a library for using the Spotify API; the existing rspotify seems to be tied to tokio through its dependence on reqwest. I'd like this library to be executor-independent, and looking through some recent discussions led me to this crate in particular.

I was initially a bit disappointed to see that there was no client_tokio example or something similar given that it's been claimed that async-h1 doesn't care about the server it's running with. After trying to make such an example myself, I seemed to run into a bigger issue: because tokio's TcpStream doesn't implement futures::io::AsyncRead (it implements tokio::io::AsyncRead instead), I think the orphan rule prevents async-h1 from working with tokio.

Am I crazy, or is it currently impossible to use this crate with tokio?


EDIT: After further research, it appears like this can be accomplished using a compatibility wrapper, there's an example here: https://github.com/Nemo157/futures-tokio-compat/blob/master/src/lib.rs (which seems to have an additional benefit of not allocating the Pin, meaning this should be zero-cost?). I'm planning to keep working on it, if I finish up an example, is that something this project is interested in having added to the examples?

bspeice avatar May 10 '20 22:05 bspeice

@bspeice Thanks for writing! Yes, this should be possible. An example would be super helpful. And if you run into any bugs let us know ❤️🦀

rylev avatar May 15 '20 09:05 rylev

Apologies that it's taken me so long. Haven't opened a PR yet, because I would appreciate some guidance.

Here's what I have so far:

Client

use http_types::{Error, Method, Request, Url};
use async_h1::client;
use tokio::net::TcpStream as TokioStream;
use tokio_util::compat::Tokio02AsyncReadCompatExt;

#[tokio::main]
async fn main() -> Result<(), Error> {

    // Note that unlike the async-std version, we create a new TcpStream each time.
    for i in 0usize..2 {
        let stream = TokioStream::connect("127.0.0.1:8080").await?;
        let peer_addr = stream.peer_addr()?;
        let stream = stream.compat();
        println!("connecting to {}", peer_addr);

        println!("making request {}/2", i + 1);
        let url = Url::parse(&format!("http://{}/foo", peer_addr)).unwrap();
        let req = Request::new(Method::Get, url);
        let res = client::connect(stream, req).await;
        println!("{:?}", res);
    }

    Ok(())
}

The issue is that async-h1 assumes the TcpStream is clonable, which is true for async-std, but not for tokio. This isn't so bad for the client, because we can just open new connections, but is problematic for the server.

I haven't been able to figure out a good way of hacking in Clone implementations; AsyncWrite isn't implemented for Arc<T>, and it feels a bit silly to build a LocalCompat<Arc<RemoteCompat<TcpStream>>> in order to give this to async-h1. As far as I can tell, the Clone is needed because server::decode needs a 'static reader, but I'm not totally sure on that one.

So I can hack together a client implementation, but the server implementation seems to have issues because Tokio's TcpStream doesn't allow cloning. Is there a "good" way to remove the Clone requirement?

bspeice avatar May 23 '20 21:05 bspeice