use std::io;
use bytes::Bytes;
use http::{Request, Response, Uri};
use tokio::io::{AsyncRead, AsyncWrite};
use tokio::net::{TcpListener, TcpStream};
use crate::models::readers::http::host_port;
use crate::models::streams::http::{H2RecvRead, H2SendWrite};
use crate::traits::transport::Transport;
const INITIAL_WINDOW: u32 = 8 * 1024 * 1024;
pub struct HttpTransport;
impl HttpTransport {
pub async fn connect(url: &str) -> io::Result<(H2RecvRead, H2SendWrite)> {
let uri: Uri = url
.parse()
.map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
let (host, port) = host_port(&uri, "http", 80)?;
let tcp = TcpStream::connect((host.as_str(), port)).await?;
connect_exchange(tcp, uri).await
}
#[cfg(feature = "tls")]
pub async fn connect_tls(
url: &str,
config: std::sync::Arc<tokio_rustls::rustls::ClientConfig>,
) -> io::Result<(H2RecvRead, H2SendWrite)> {
let uri: Uri = url
.parse()
.map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
let (host, port) = host_port(&uri, "https", 443)?;
let server_name = rustls_pki_types::ServerName::try_from(host.clone())
.map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
let tcp = TcpStream::connect((host.as_str(), port)).await?;
let connector = tokio_rustls::TlsConnector::from(config);
let tls = connector.connect(server_name, tcp).await?;
if tls.get_ref().1.alpn_protocol() != Some(b"h2") {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"TLS ALPN did not negotiate h2; set \
config.alpn_protocols = vec![b\"h2\".to_vec()] on ClientConfig",
));
}
connect_exchange(tls, uri).await
}
pub async fn bind(url: &str) -> io::Result<TcpListener> {
let uri: Uri = url
.parse()
.map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
let scheme = uri.scheme_str().unwrap_or("http");
let default_port = if scheme == "https" { 443 } else { 80 };
let (host, port) = host_port(&uri, scheme, default_port)?;
TcpListener::bind((host.as_str(), port)).await
}
pub async fn accept(listener: &TcpListener) -> io::Result<(H2RecvRead, H2SendWrite)> {
let (tcp, _) = listener.accept().await?;
accept_exchange(tcp).await
}
#[cfg(feature = "tls")]
pub async fn accept_tls(
listener: &TcpListener,
config: std::sync::Arc<tokio_rustls::rustls::ServerConfig>,
) -> io::Result<(H2RecvRead, H2SendWrite)> {
let (tcp, _) = listener.accept().await?;
let acceptor = tokio_rustls::TlsAcceptor::from(config);
let tls = acceptor.accept(tcp).await?;
accept_exchange(tls).await
}
}
async fn connect_exchange<T>(io_stream: T, uri: Uri) -> io::Result<(H2RecvRead, H2SendWrite)>
where
T: AsyncRead + AsyncWrite + Send + Unpin + 'static,
{
let (mut send_request, connection) = h2::client::Builder::new()
.initial_window_size(INITIAL_WINDOW)
.initial_connection_window_size(INITIAL_WINDOW)
.handshake::<_, Bytes>(io_stream)
.await
.map_err(io::Error::other)?;
tokio::spawn(async move {
if let Err(e) = connection.await {
tracing::debug!("h2 connection driver exited: {e}");
}
});
let req = Request::post(uri)
.body(())
.map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
let (response_fut, send_stream) = send_request
.send_request(req, false)
.map_err(io::Error::other)?;
let response = response_fut.await.map_err(io::Error::other)?;
let recv = response.into_body();
Ok((H2RecvRead::new(recv), H2SendWrite::new(send_stream)))
}
async fn accept_exchange<T>(io_stream: T) -> io::Result<(H2RecvRead, H2SendWrite)>
where
T: AsyncRead + AsyncWrite + Send + Unpin + 'static,
{
let mut conn = h2::server::Builder::new()
.initial_window_size(INITIAL_WINDOW)
.initial_connection_window_size(INITIAL_WINDOW)
.handshake::<_, Bytes>(io_stream)
.await
.map_err(io::Error::other)?;
let (req, mut respond) = conn
.accept()
.await
.ok_or_else(|| {
io::Error::new(
io::ErrorKind::UnexpectedEof,
"connection closed before a request arrived",
)
})?
.map_err(io::Error::other)?;
let recv = req.into_body();
let response = Response::builder()
.status(200)
.body(())
.map_err(io::Error::other)?;
let send_stream = respond
.send_response(response, false)
.map_err(io::Error::other)?;
tokio::spawn(async move { while conn.accept().await.is_some() {} });
Ok((H2RecvRead::new(recv), H2SendWrite::new(send_stream)))
}
impl Transport for HttpTransport {
type Endpoint = str;
type Listener = TcpListener;
type Read = H2RecvRead;
type Write = H2SendWrite;
async fn connect(endpoint: &str) -> io::Result<(H2RecvRead, H2SendWrite)> {
HttpTransport::connect(endpoint).await
}
async fn bind(endpoint: &str) -> io::Result<TcpListener> {
HttpTransport::bind(endpoint).await
}
async fn accept(listener: &TcpListener) -> io::Result<(H2RecvRead, H2SendWrite)> {
HttpTransport::accept(listener).await
}
}