//! An HTTP+TLS client based on `hyper` and `async-native-tls`. //! //! Run with: //! //! ``` //! cargo run --example hyper-client //! ``` use std::net::Shutdown; use std::net::{TcpStream, ToSocketAddrs}; use std::pin::Pin; use std::task::{Context, Poll}; use anyhow::{bail, Context as _, Error, Result}; use async_native_tls::TlsStream; use http::Uri; use hyper::{Body, Client, Request, Response}; use smol::{io, prelude::*, Async}; /// Sends a request and fetches the response. async fn fetch(req: Request) -> Result> { Ok(Client::builder() .executor(SmolExecutor) .build::<_, Body>(SmolConnector) .request(req) .await?) } fn main() -> Result<()> { smol::block_on(async { // Create a request. let req = Request::get("https://www.rust-lang.org").body(Body::empty())?; // Fetch the response. let resp = fetch(req).await?; println!("{:#?}", resp); // Read the message body. let body = resp .into_body() .try_fold(Vec::new(), |mut body, chunk| { body.extend_from_slice(&chunk); Ok(body) }) .await?; println!("{}", String::from_utf8_lossy(&body)); Ok(()) }) } /// Spawns futures. #[derive(Clone)] struct SmolExecutor; impl hyper::rt::Executor for SmolExecutor { fn execute(&self, fut: F) { smol::spawn(async { drop(fut.await) }).detach(); } } /// Connects to URLs. #[derive(Clone)] struct SmolConnector; impl hyper::service::Service for SmolConnector { type Response = SmolStream; type Error = Error; type Future = Pin> + Send>>; fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } fn call(&mut self, uri: Uri) -> Self::Future { Box::pin(async move { let host = uri.host().context("cannot parse host")?; match uri.scheme_str() { Some("http") => { let socket_addr = { let host = host.to_string(); let port = uri.port_u16().unwrap_or(80); smol::unblock(move || (host.as_str(), port).to_socket_addrs()) .await? .next() .context("cannot resolve address")? }; let stream = Async::::connect(socket_addr).await?; Ok(SmolStream::Plain(stream)) } Some("https") => { // In case of HTTPS, establish a secure TLS connection first. let socket_addr = { let host = host.to_string(); let port = uri.port_u16().unwrap_or(443); smol::unblock(move || (host.as_str(), port).to_socket_addrs()) .await? .next() .context("cannot resolve address")? }; let stream = Async::::connect(socket_addr).await?; let stream = async_native_tls::connect(host, stream).await?; Ok(SmolStream::Tls(stream)) } scheme => bail!("unsupported scheme: {:?}", scheme), } }) } } /// A TCP or TCP+TLS connection. enum SmolStream { /// A plain TCP connection. Plain(Async), /// A TCP connection secured by TLS. Tls(TlsStream>), } impl hyper::client::connect::Connection for SmolStream { fn connected(&self) -> hyper::client::connect::Connected { hyper::client::connect::Connected::new() } } impl tokio::io::AsyncRead for SmolStream { fn poll_read( mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut tokio::io::ReadBuf<'_>, ) -> Poll> { match &mut *self { SmolStream::Plain(s) => { Pin::new(s) .poll_read(cx, buf.initialize_unfilled()) .map_ok(|size| { buf.advance(size); }) } SmolStream::Tls(s) => { Pin::new(s) .poll_read(cx, buf.initialize_unfilled()) .map_ok(|size| { buf.advance(size); }) } } } } impl tokio::io::AsyncWrite for SmolStream { fn poll_write( mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { match &mut *self { SmolStream::Plain(s) => Pin::new(s).poll_write(cx, buf), SmolStream::Tls(s) => Pin::new(s).poll_write(cx, buf), } } fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { match &mut *self { SmolStream::Plain(s) => Pin::new(s).poll_flush(cx), SmolStream::Tls(s) => Pin::new(s).poll_flush(cx), } } fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { match &mut *self { SmolStream::Plain(s) => { s.get_ref().shutdown(Shutdown::Write)?; Poll::Ready(Ok(())) } SmolStream::Tls(s) => Pin::new(s).poll_close(cx), } } }