use std::io;
use std::pin::Pin;
use std::task::{Context, Poll};
use futures_core::Stream;
use http::{Request, Uri};
use minarrow::{Field, SuperTable, Table, Vec64};
use tokio::io::AsyncWriteExt;
use tokio::net::{TcpListener, TcpStream};
use crate::enums::{BufferChunkSize, IPCMessageProtocol};
use crate::models::readers::ipc::table::TableReader;
use crate::models::decoders::limits::DecodeLimits;
use crate::models::streams::http::{H2RecvRead, H2SendWrite, HttpByteStream};
use crate::models::transports::http::HttpTransport;
use crate::traits::transport_reader::IPCTransportReader;
pub struct HttpTableReader {
inner: TableReader<Vec64<u8>>,
}
impl HttpTableReader {
pub async fn get(url: &str, limits: Option<DecodeLimits>) -> io::Result<Self> {
let req = parse_get(url)?;
Self::from_request(req, limits).await
}
#[cfg(feature = "tls")]
pub async fn get_tls(
url: &str,
config: std::sync::Arc<tokio_rustls::rustls::ClientConfig>,
limits: Option<DecodeLimits>,
) -> io::Result<Self> {
let req = parse_get(url)?;
Self::from_request_tls(req, config, limits).await
}
pub async fn from_request(
req: Request<()>,
limits: Option<DecodeLimits>,
) -> io::Result<Self> {
let (host, port) = host_port(req.uri(), "http", 80)?;
let tcp = TcpStream::connect((host.as_str(), port)).await?;
let recv = h2_send_get(tcp, req).await?;
Ok(Self::from_recv(recv, limits))
}
#[cfg(feature = "tls")]
pub async fn from_request_tls(
req: Request<()>,
config: std::sync::Arc<tokio_rustls::rustls::ClientConfig>,
limits: Option<DecodeLimits>,
) -> io::Result<Self> {
let (host, port) = host_port(req.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",
));
}
let recv = h2_send_get(tls, req).await?;
Ok(Self::from_recv(recv, limits))
}
pub fn from_recv(recv: h2::RecvStream, limits: Option<DecodeLimits>) -> Self {
let stream =
HttpByteStream::new(H2RecvRead::new(recv), crate::enums::BufferChunkSize::Http);
let inner = TableReader::<Vec64<u8>>::new(
stream,
BufferChunkSize::Http.chunk_size(),
IPCMessageProtocol::Stream,
limits,
);
Self { inner }
}
pub async fn accept(
listener: &TcpListener,
limits: Option<DecodeLimits>,
) -> io::Result<Self> {
let (recv_read, send_write) = HttpTransport::accept(listener).await?;
Self::from_exchange(recv_read, send_write, limits).await
}
pub async fn from_exchange(
recv_read: H2RecvRead,
mut send_write: H2SendWrite,
limits: Option<DecodeLimits>,
) -> io::Result<Self> {
send_write.shutdown().await?;
let stream = HttpByteStream::new(recv_read, BufferChunkSize::Http);
let inner = TableReader::<Vec64<u8>>::new(
stream,
BufferChunkSize::Http.chunk_size(),
IPCMessageProtocol::Stream,
limits,
);
Ok(Self { inner })
}
}
impl IPCTransportReader for HttpTableReader {
async fn read_all_tables(self) -> io::Result<Vec<Table>> {
self.inner.read_all_tables().await
}
async fn read_tables(self, n: Option<usize>) -> io::Result<Vec<Table>> {
self.inner.read_tables(n).await
}
async fn read_to_super_table(
self,
name: Option<String>,
n: Option<usize>,
) -> io::Result<SuperTable> {
self.inner.read_to_super_table(name, n).await
}
async fn combine_to_table(self, name: Option<String>) -> io::Result<Table> {
self.inner.combine_to_table(name).await
}
fn schema(&self) -> Option<&[Field]> {
self.inner.schema()
}
async fn read_next(&mut self) -> io::Result<Option<Table>> {
self.inner.read_next().await
}
}
impl Stream for HttpTableReader {
type Item = io::Result<Table>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let me = self.get_mut();
Pin::new(&mut me.inner).poll_next(cx)
}
}
fn parse_get(url: &str) -> io::Result<Request<()>> {
let uri: Uri = url
.parse()
.map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
Request::get(uri)
.body(())
.map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))
}
pub(crate) fn host_port(
uri: &Uri,
expected_scheme: &str,
default_port: u16,
) -> io::Result<(String, u16)> {
let scheme = uri
.scheme_str()
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "uri missing scheme"))?;
if scheme != expected_scheme {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("expected scheme {expected_scheme}, got {scheme}"),
));
}
let host = uri
.host()
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "uri missing host"))?
.to_string();
let port = uri.port_u16().unwrap_or(default_port);
Ok((host, port))
}
const INITIAL_WINDOW: u32 = 8 * 1024 * 1024;
async fn h2_send_get<T>(io: T, req: Request<()>) -> io::Result<h2::RecvStream>
where
T: tokio::io::AsyncRead + tokio::io::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::Bytes>(io)
.await
.map_err(io::Error::other)?;
tokio::spawn(async move {
if let Err(e) = connection.await {
tracing::debug!("h2 connection driver exited: {e}");
}
});
let (response_fut, _send_stream) = send_request
.send_request(req, true)
.map_err(io::Error::other)?;
let response = response_fut.await.map_err(io::Error::other)?;
Ok(response.into_body())
}