yaaral 0.5.3

yet another async runtime abstraction library
Documentation
//! An HTTP+TLS client based on `hyper` and `async-native-tls`.

/// This file was adapted from the smol examples, licensed under Mit.
use std::pin::Pin;
use std::task::{Context, Poll};

use anyhow::{Context as _, Result, bail};
use async_native_tls::TlsStream;
use bytes::Bytes;
use http_body_util::Full;
use hyper::body::Incoming;
use hyper::{Request, Response};
use smol::{io, net::TcpStream, prelude::*};
use smol_hyper::rt::FuturesIo;

use crate::TaskHandle;

/// Sends a request and fetches the response.
pub(crate) async fn fetch(
    ex: &impl crate::TaskInterface,
    mut req: Request<Full<Bytes>>,
) -> Result<Response<Incoming>> {
    // Connect to the HTTP server.
    let io = {
        let host = req.uri().host().context("cannot parse host")?.to_string();
        let host_header = match req.uri().port_u16() {
            Some(port) => format!("{}:{}", host, port),
            None => host.to_string(),
        };
        req.headers_mut().insert(
            http::header::HOST,
            http::HeaderValue::from_str(&host_header)?,
        );
        match req.uri().scheme_str() {
            Some("http") => {
                let stream = {
                    let port = req.uri().port_u16().unwrap_or(80);
                    TcpStream::connect((host, port)).await?
                };
                SmolStream::Plain(stream)
            }
            Some("https") => {
                // In case of HTTPS, establish a secure TLS connection first.
                let stream = {
                    let port = req.uri().port_u16().unwrap_or(443);
                    TcpStream::connect((host.as_str(), port)).await?
                };
                let stream = async_native_tls::connect(host, stream).await?;
                SmolStream::Tls(stream)
            }
            scheme => bail!("unsupported scheme: {:?}", scheme),
        }
    };

    // Spawn the HTTP/1 connection.
    let (mut sender, conn) = hyper::client::conn::http1::handshake(FuturesIo::new(io)).await?;
    ex.spawn_task(async move {
        if let Err(e) = conn.await {
            println!("Connection failed: {:?}", e);
        }
    })
    .unwrap()
    .detach();

    // Get the result
    let mut uri_parts = http::uri::Parts::default();
    uri_parts.path_and_query = req.uri().path_and_query().cloned();
    // Make sure the path is not empty.
    match &uri_parts.path_and_query {
        None => {
            uri_parts.path_and_query = Some(http::uri::PathAndQuery::from_static("/"));
        }
        Some(paq) => {
            // If the original uri is 'http://localhost' then the path is empty, and somehow hyper will
            // send an empty path, which is illegal. But http Uri still return '/' for calling path(),
            // so just to be sure if path is / and there is no query, make sure there is a non-empty
            // path.
            if paq.path() == "/" && paq.query().is_none() {
                uri_parts.path_and_query = Some(http::uri::PathAndQuery::from_static("/"));
            }
        }
    }
    *req.uri_mut() = http::Uri::from_parts(uri_parts)?;
    let result = sender.send_request(req).await?;
    Ok(result)
}

/// A TCP or TCP+TLS connection.
enum SmolStream {
    /// A plain TCP connection.
    Plain(TcpStream),

    /// A TCP connection secured by TLS.
    Tls(TlsStream<TcpStream>),
}

impl AsyncRead for SmolStream {
    fn poll_read(
        mut self: Pin<&mut Self>,
        cx: &mut Context<'_>,
        buf: &mut [u8],
    ) -> Poll<io::Result<usize>> {
        match &mut *self {
            SmolStream::Plain(stream) => Pin::new(stream).poll_read(cx, buf),
            SmolStream::Tls(stream) => Pin::new(stream).poll_read(cx, buf),
        }
    }
}

impl AsyncWrite for SmolStream {
    fn poll_write(
        mut self: Pin<&mut Self>,
        cx: &mut Context<'_>,
        buf: &[u8],
    ) -> Poll<io::Result<usize>> {
        match &mut *self {
            SmolStream::Plain(stream) => Pin::new(stream).poll_write(cx, buf),
            SmolStream::Tls(stream) => Pin::new(stream).poll_write(cx, buf),
        }
    }

    fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
        match &mut *self {
            SmolStream::Plain(stream) => Pin::new(stream).poll_close(cx),
            SmolStream::Tls(stream) => Pin::new(stream).poll_close(cx),
        }
    }

    fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
        match &mut *self {
            SmolStream::Plain(stream) => Pin::new(stream).poll_flush(cx),
            SmolStream::Tls(stream) => Pin::new(stream).poll_flush(cx),
        }
    }
}