use eyre::Context;
use http::header;
use tracing::debug;
use crate::{types::Request, util::read_and_parse, Body, HeadersExt, Response};
use fluke_buffet::{
PieceList, RollMut, {ReadOwned, WriteOwned},
};
use super::{
body::{write_h1_body, BodyWriteMode, H1Body, H1BodyKind},
encode::encode_request,
};
pub struct ClientConf {}
#[allow(async_fn_in_trait)] pub trait ClientDriver {
type Return;
async fn on_informational_response(&mut self, res: Response) -> eyre::Result<()>;
async fn on_final_response(
self,
res: Response,
body: &mut impl Body,
) -> eyre::Result<Self::Return>;
}
pub async fn request<R, W, D>(
(mut transport_r, mut transport_w): (R, W),
mut req: Request,
body: &mut impl Body,
driver: D,
) -> eyre::Result<(Option<(R, W)>, D::Return)>
where
R: ReadOwned,
W: WriteOwned,
D: ClientDriver,
{
let mode = match body.content_len() {
Some(0) => BodyWriteMode::Empty,
Some(len) => {
req.headers
.insert(header::CONTENT_LENGTH, len.to_string().into_bytes().into());
BodyWriteMode::ContentLength
}
None => BodyWriteMode::Chunked,
};
let mut buf = RollMut::alloc()?;
let mut list = PieceList::default();
encode_request(req, &mut list, &mut buf)?;
transport_w
.writev_all_owned(list)
.await
.wrap_err("writing request headers")?;
let send_body_fut = {
async move {
match write_h1_body(&mut transport_w, body, mode).await {
Err(err) => {
panic!("error writing request body: {err:?}");
}
Ok(_) => {
debug!("done writing request body");
Ok::<_, eyre::Report>(transport_w)
}
}
}
};
let recv_res_fut = {
async move {
let (buf, res) = read_and_parse(
super::parse::response,
&mut transport_r,
buf,
64 * 1024,
)
.await
.map_err(|e| eyre::eyre!("error reading response headers from server: {e:?}"))?
.ok_or_else(|| eyre::eyre!("server went away before sending response headers"))?;
debug!("client received response");
res.debug_print();
if res.status.is_informational() {
todo!("handle informational responses");
}
let chunked = res.headers.is_chunked_transfer_encoding();
let content_len = res.headers.content_length().unwrap_or_default();
let mut res_body = H1Body::new(
transport_r,
buf,
if chunked {
H1BodyKind::Chunked
} else {
H1BodyKind::ContentLength(content_len)
},
);
let conn_close = res.headers.is_connection_close();
let ret = driver.on_final_response(res, &mut res_body).await?;
let transport_r = match (conn_close, res_body.into_inner()) {
(false, Some((_buf, transport_r))) => Some(transport_r),
_ => None,
};
Ok((transport_r, ret))
}
};
let (send_res, recv_res) = tokio::try_join!(send_body_fut, recv_res_fut)?;
let transport_w = send_res;
let (transport_r, ret) = recv_res;
let transport = transport_r.map(|transport_r| (transport_r, transport_w));
Ok((transport, ret))
}