use std::pin::Pin;
use bytes::{Buf, Bytes};
use http_body::Body;
use super::client::{stream_error_to_error, stream_error_to_h3_error};
use crate::{
client::core::{
Error,
body::{DecodedLength, Incoming as IncomingBody},
error::BoxError,
},
error::H3Error,
};
pub(crate) enum RecvResponseResult {
Success(http::Response<()>),
EarlyReturn(http::Response<IncomingBody>),
}
#[allow(clippy::result_large_err)]
pub(crate) async fn send_request_body<B>(
stream: &mut hpx_h3::client::RequestStream<hpx_h3_quinn::BidiStream<Bytes>, Bytes>,
body: &mut B,
) -> Result<(), (Error, Option<http::Request<B>>)>
where
B: Body + Send + Unpin,
B::Data: Into<Bytes>,
B::Error: Into<BoxError>,
{
loop {
let frame = match std::future::poll_fn(|cx| Pin::new(&mut *body).poll_frame(cx)).await {
Some(Ok(frame)) => frame,
Some(Err(e)) => {
return Err((Error::new_body(e.into()), None::<http::Request<B>>));
}
None => break,
};
if let Ok(data) = frame.into_data() {
stream
.send_data(data.into())
.await
.map_err(|e| (stream_error_to_error(e), None::<http::Request<B>>))?;
}
}
stream
.finish()
.await
.map_err(|e| (stream_error_to_error(e), None::<http::Request<B>>))?;
Ok(())
}
#[allow(clippy::result_large_err)]
pub(crate) async fn handle_response<B>(
stream: &mut hpx_h3::client::RequestStream<hpx_h3_quinn::BidiStream<Bytes>, Bytes>,
) -> Result<RecvResponseResult, (Error, Option<http::Request<B>>)> {
let response = match stream.recv_response().await {
Ok(response) => response,
Err(e) => {
if matches!(
&e,
hpx_h3::error::StreamError::RemoteTerminate { code, .. }
if code.value() == hpx_h3::error::Code::H3_NO_ERROR.value()
) {
let (body_tx, body_rx) =
IncomingBody::new_channel(DecodedLength::CHUNKED, false);
drop(body_tx); let response = http::Response::builder()
.status(http::StatusCode::OK)
.body(body_rx)
.map_err(|_| {
(
Error::new_body(H3Error::Other(Box::new(e))),
None::<http::Request<B>>,
)
})?;
return Ok(RecvResponseResult::EarlyReturn(response));
}
let h3_err = stream_error_to_h3_error(e);
let (mut body_tx, body_rx) =
IncomingBody::new_channel(DecodedLength::CHUNKED, false);
body_tx.send_error(Error::new_body(h3_err));
let response = http::Response::builder()
.status(http::StatusCode::OK)
.body(body_rx)
.map_err(|_| {
(
Error::new_body(H3Error::StreamReset {
code: 0x0102,
stream_id: 0,
}),
None::<http::Request<B>>,
)
})?;
return Ok(RecvResponseResult::EarlyReturn(response));
}
};
Ok(RecvResponseResult::Success(response))
}
#[allow(clippy::result_large_err)]
pub(crate) async fn collect_response_body<B>(
stream: &mut hpx_h3::client::RequestStream<hpx_h3_quinn::BidiStream<Bytes>, Bytes>,
response: http::Response<()>,
) -> Result<http::Response<IncomingBody>, (Error, Option<http::Request<B>>)> {
let mut chunks: Vec<Bytes> = Vec::new();
while let Some(mut data) = stream
.recv_data()
.await
.map_err(|e| (stream_error_to_error(e), None::<http::Request<B>>))?
{
let len = data.remaining();
chunks.push(data.copy_to_bytes(len));
}
let (mut body_tx, body_rx) =
IncomingBody::new_channel(DecodedLength::CHUNKED, false);
let send_task = tokio::spawn(async move {
for chunk in chunks {
if std::future::poll_fn(|cx| body_tx.poll_ready(cx))
.await
.is_err()
{
return;
}
if body_tx.try_send_data(chunk).is_err() {
return;
}
}
drop(body_tx);
});
drop(send_task);
let response = response.map(|_| body_rx);
Ok(response)
}