async-h1
async-h1 copied to clipboard
Tokio server/client example?
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 Thanks for writing! Yes, this should be possible. An example would be super helpful. And if you run into any bugs let us know ❤️🦀
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?