use std::{
future::Future,
pin::Pin,
sync::Arc,
task::{Context, Poll},
};
use bytes::{BufMut, Bytes, BytesMut};
use http::HeaderMap;
use http_body::{Body, Frame};
use super::StreamRef;
use crate::{
h3::{
client::{
AbortOnDrop,
app::{Http3ClientApp, ResponseHead, ResponseHeadState},
error::RequestError,
},
common::{H3Error, ReadState, read::poll_read_frame},
},
quic::connection::{QuicScionConn, WeakConnectionHandle},
};
pub struct ResponseFut {
handle: WeakConnectionHandle<Http3ClientApp>,
stream_id: u64,
read_guard: Option<ReadGuard>,
}
impl ResponseFut {
pub(crate) fn new(read_guard: ReadGuard) -> Self {
Self {
handle: read_guard.handle(),
stream_id: read_guard.stream_id(),
read_guard: Some(read_guard),
}
}
}
impl Future for ResponseFut {
type Output = Result<http::Response<H3ResponseBody>, RequestError>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
match poll_head(&this.handle, this.stream_id, cx) {
Poll::Pending => Poll::Pending,
Poll::Ready(Ok(head)) => {
let guard = this
.read_guard
.take()
.expect("ResponseFut polled after completion");
Poll::Ready(Ok(head_into_response(head, H3ResponseBody::new(guard))))
}
Poll::Ready(Err(err)) => {
if let Some(guard) = this.read_guard.as_mut() {
guard.mark_done();
}
Poll::Ready(Err(err))
}
}
}
}
fn head_into_response(head: ResponseHead, body: H3ResponseBody) -> http::Response<H3ResponseBody> {
let mut response = http::Response::builder()
.status(head.status)
.body(body)
.expect("status is valid");
*response.headers_mut() = head.headers;
response
}
pub struct H3ResponseBody {
handle: WeakConnectionHandle<Http3ClientApp>,
stream_id: u64,
read_guard: ReadGuard,
upload: Option<AbortOnDrop<()>>,
}
impl H3ResponseBody {
pub(crate) fn new(read_guard: ReadGuard) -> Self {
Self {
handle: read_guard.handle(),
stream_id: read_guard.stream_id(),
read_guard,
upload: None,
}
}
pub(crate) fn attach_upload(&mut self, upload: AbortOnDrop<()>) {
self.upload = Some(upload);
}
pub async fn next_frame(&mut self) -> Option<Result<Frame<Bytes>, H3Error>> {
futures::future::poll_fn(|cx| poll_read_frame(&self.handle, self.stream_id, cx)).await
}
pub async fn collect_to_buf(
mut self,
buffer: &mut impl BufMut,
max_size: Option<usize>,
) -> Result<(usize, Option<HeaderMap>), CollectError> {
let mut written = 0;
while let Some(frame) = self.next_frame().await {
let frame = frame?;
if frame.is_data() {
let chunk = frame.into_data().expect("frame is data");
if chunk.len() > buffer.remaining_mut() {
return Err(CollectError::BufferTooSmall);
}
if let Some(max) = max_size
&& written + chunk.len() > max
{
return Err(CollectError::TooLarge);
}
buffer.put_slice(&chunk);
written += chunk.len();
} else if frame.is_trailers() {
let trailers = frame.into_trailers().expect("frame is trailers");
return Ok((written, Some(trailers)));
} else {
debug_assert!(false, "Unexpected frame type in response body: {:?}", frame);
}
}
Ok((written, None))
}
pub async fn bytes(
self,
max_size: Option<usize>,
) -> Result<(BytesMut, Option<HeaderMap>), CollectError> {
let mut buffer = BytesMut::new();
let (_written, trailers) = self.collect_to_buf(&mut buffer, max_size).await?;
Ok((buffer, trailers))
}
pub async fn text(
self,
max_size: Option<usize>,
) -> Result<(String, Option<HeaderMap>), CollectToStringError> {
let (bytes, trailers) = match self.bytes(max_size).await {
Ok(result) => result,
Err(e) => {
match e {
CollectError::H3(h3_err) => return Err(CollectToStringError::H3(h3_err)),
CollectError::TooLarge => return Err(CollectToStringError::TooLarge),
CollectError::BufferTooSmall => {
unreachable!(
"BytesMut grows automatically, cannot be BufferTooSmall in collect_to_bytes"
)
}
}
}
};
let string = String::from_utf8(bytes.into())?;
Ok((string, trailers))
}
}
#[derive(Debug, thiserror::Error)]
pub enum CollectError {
#[error(transparent)]
H3(#[from] H3Error),
#[error("the provided buffer was too small to hold the response body")]
BufferTooSmall,
#[error("response body exceeded maximum size")]
TooLarge,
}
#[derive(Debug, thiserror::Error)]
pub enum CollectToStringError {
#[error(transparent)]
H3(#[from] H3Error),
#[error("response body exceeded maximum size")]
TooLarge,
#[error("response body did not contain valid UTF-8")]
Utf8(#[from] std::string::FromUtf8Error),
}
impl Body for H3ResponseBody {
type Data = Bytes;
type Error = H3Error;
fn poll_frame(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
let this = self.get_mut();
if this.read_guard.is_done() {
return Poll::Ready(None);
}
let res = poll_read_frame(&this.handle, this.stream_id, cx);
if let Poll::Ready(ready) = &res {
match ready {
None => this.read_guard.mark_done(),
Some(Err(_)) => this.read_guard.mark_done(),
Some(Ok(frame)) if frame.is_trailers() => this.read_guard.mark_done(),
Some(Ok(_)) => {}
}
}
res
}
}
pub(crate) struct ReadGuard {
stream_ref: Arc<StreamRef>,
done: bool,
}
impl ReadGuard {
pub(crate) fn new(stream_ref: Arc<StreamRef>) -> Self {
Self {
stream_ref,
done: false,
}
}
pub(crate) fn handle(&self) -> WeakConnectionHandle<Http3ClientApp> {
self.stream_ref.handle()
}
pub(crate) fn stream_id(&self) -> u64 {
self.stream_ref.stream_id()
}
pub(crate) fn is_done(&self) -> bool {
self.done
}
pub(crate) fn mark_done(&mut self) {
self.done = true;
}
}
impl Drop for ReadGuard {
fn drop(&mut self) {
if !self.done {
shutdown_read(&self.stream_ref.handle(), self.stream_ref.stream_id());
}
}
}
fn shutdown_read(handle: &WeakConnectionHandle<Http3ClientApp>, stream_id: u64) {
let Some(handle) = handle.upgrade() else {
return;
};
let mut guard = handle.lock_recovering();
let _ = guard
.inner
.stream_shutdown(stream_id, squiche::Shutdown::Read, 0);
drop(guard);
handle.notify();
}
pub(crate) fn poll_head(
handle: &WeakConnectionHandle<Http3ClientApp>,
stream_id: u64,
cx: &mut Context<'_>,
) -> Poll<Result<ResponseHead, RequestError>> {
let Some(handle) = handle.upgrade() else {
return Poll::Ready(Err(RequestError::ConnectionClosed));
};
let mut guard = handle.lock();
let QuicScionConn { app, .. } = &mut *guard;
let Http3ClientApp {
response_heads,
streams,
locally_closed,
..
} = app;
let Some(slot) = response_heads.get_mut(&stream_id) else {
return Poll::Ready(Err(RequestError::ConnectionClosed));
};
match slot.state {
ResponseHeadState::Arrived(_) => {
let ResponseHeadState::Arrived(head) =
std::mem::replace(&mut slot.state, ResponseHeadState::Done)
else {
unreachable!("just matched Arrived")
};
Poll::Ready(Ok(head))
}
ResponseHeadState::Waiting => {
match streams.get(&stream_id) {
Some(st) => {
match st.read_state {
ReadState::Closed if *locally_closed => {
return Poll::Ready(Err(RequestError::LocallyClosed));
}
ReadState::Closed => {
return Poll::Ready(Err(RequestError::ConnectionClosed));
}
ReadState::Reset(code) => {
return Poll::Ready(Err(RequestError::Reset(code)));
}
ReadState::Streaming | ReadState::Trailers(_) | ReadState::Eof => {}
}
}
None => {
return Poll::Ready(Err(RequestError::ConnectionClosed));
}
}
slot.waker = Some(cx.waker().clone());
Poll::Pending
}
ResponseHeadState::Done => {
Poll::Ready(Err(RequestError::ConnectionClosed))
}
}
}