use std::sync::Arc;
use foundation_core::url::Uri;
use foundation_core::valtron::{self, CollectionState, Pipe, StreamIteratorExt, TaskIteratorExt};
use foundation_netio::shared::client::request_task::HttpExchange;
use foundation_netio::shared::client::PreparedRequest;
use foundation_netio::DynNetClient;
use foundation_netio::shared::http::Extensions;
use foundation_netio::shared::http::{
pushable_request_body_with_depth, HttpClientError, Proto, RequestDescriptor,
SimpleHeader, SimpleHeaders, DEFAULT_PUSHABLE_DEPTH,
};
use foundation_netio::HttpClientBuilder;
use super::{
BodyStream, ByteSink, HeadStream, Transport, TransportCapabilities, TransportError,
TransportStream,
};
#[derive(Clone)]
pub struct H1Transport {
client: DynNetClient,
}
impl H1Transport {
#[must_use]
pub fn new(client: DynNetClient) -> Self {
Self { client }
}
}
impl Default for H1Transport {
fn default() -> Self {
Self::new(HttpClientBuilder::new().build())
}
}
impl Transport for H1Transport {
fn capabilities(&self) -> TransportCapabilities {
TransportCapabilities {
request_streaming: true,
full_duplex: false,
h2_trailers: false,
http_versions: &[Proto::HTTP11],
multiplexed: false,
}
}
fn open(&self, request: RequestDescriptor) -> Result<TransportStream, TransportError> {
let url_str = request_url_string(&request);
let method = request.method.clone();
let mut headers = request.headers.clone();
if !headers.contains_key(&SimpleHeader::HOST) {
let host_str = request.request_uri.host_str().unwrap_or_default();
let host = if request.request_uri.port().is_some() {
format!("{host_str}:{}", request.request_uri.port_or_default())
} else {
host_str
};
headers.insert(SimpleHeader::HOST, vec![host]);
}
let (pushable, body_stream) = pushable_request_body_with_depth(DEFAULT_PUSHABLE_DEPTH);
let send_body: ByteSink = pushable.into_sender();
let uri = Uri::parse(&url_str).map_err(|e| {
http_to_transport_error(HttpClientError::Reason(format!("invalid URL: {e}")))
})?;
let prepared = PreparedRequest {
method,
url: uri,
headers,
body: body_stream,
extensions: Extensions::new(),
};
let pump = self.client.open_exchange(prepared);
let (head_obs, head_cont) = pump.split_collect_until_map(
|item: &HttpExchange| match item {
HttpExchange::Head { status, headers } => (
CollectionState::Close(true),
Some(Ok((status.clone(), headers.clone()))),
),
HttpExchange::Failed(err) => (
CollectionState::Close(true),
Some(Err(failed_to_transport_error(err))),
),
HttpExchange::BodyChunk(_) => (CollectionState::Skip, None),
},
1,
);
let (body_obs, body_cont) = head_cont.split_collector_map(
|item: &HttpExchange| match item {
HttpExchange::BodyChunk(bytes) => (true, Some(Ok(bytes.clone()))),
HttpExchange::Failed(err) => (true, Some(Err(failed_to_transport_error(err)))),
HttpExchange::Head { .. } => (false, None),
},
DEFAULT_PUSHABLE_DEPTH,
);
let drive = body_cont.map_ready(|_| ());
valtron::send(drive).map_err(|e| {
TransportError::Connect(Arc::new(std::io::Error::new(
std::io::ErrorKind::Other,
e.to_string(),
)))
})?;
let head: HeadStream = Box::pin(head_obs.into_next_stream());
let recv_body: BodyStream = Box::pin(body_obs.into_next_stream());
let (trailer_tx, trailer_rx) = Pipe::<SimpleHeaders>::with_depth(1);
trailer_tx.close();
Ok(TransportStream {
send_body: Arc::new(send_body),
head,
recv_body,
trailers: trailer_rx,
})
}
}
fn failed_to_transport_error(
err: &Arc<dyn std::error::Error + Send + Sync + 'static>,
) -> TransportError {
TransportError::Connect(Arc::clone(err))
}
fn request_url_string(req: &RequestDescriptor) -> String {
let query = req
.request_uri
.query()
.map(|q| format!("?{q}"))
.unwrap_or_default();
format!(
"http://{}:{}{}{}",
req.request_uri
.host_str()
.unwrap_or_else(|| "localhost".to_string()),
req.request_uri.port_or_default(),
req.request_uri.path(),
query,
)
}
fn http_to_transport_error(e: HttpClientError) -> TransportError {
TransportError::Connect(Arc::new(e))
}