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;
pub(crate) async fn fetch(
ex: &impl crate::TaskInterface,
mut req: Request<Full<Bytes>>,
) -> Result<Response<Incoming>> {
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") => {
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),
}
};
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();
let mut uri_parts = http::uri::Parts::default();
uri_parts.path_and_query = req.uri().path_and_query().cloned();
match &uri_parts.path_and_query {
None => {
uri_parts.path_and_query = Some(http::uri::PathAndQuery::from_static("/"));
}
Some(paq) => {
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)
}
enum SmolStream {
Plain(TcpStream),
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),
}
}
}