use std::{
future::Future,
pin::Pin,
sync::Arc,
task::{Context, Poll},
};
use bytes::Bytes;
use http_body::{Body, Frame};
use super::StreamRef;
use crate::{
h3::{
client::{
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,
}
impl H3ResponseBody {
pub(crate) fn new(read_guard: ReadGuard) -> Self {
Self {
handle: read_guard.handle(),
stream_id: read_guard.stream_id(),
read_guard,
}
}
}
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,
..
} = 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) => {
if let ReadState::Reset(code) = st.read_state {
return Poll::Ready(Err(if code == 0 {
RequestError::ConnectionClosed
} else {
RequestError::Reset(code)
}));
}
}
None => {
return Poll::Ready(Err(RequestError::ConnectionClosed));
}
}
slot.waker = Some(cx.waker().clone());
Poll::Pending
}
ResponseHeadState::Done => {
Poll::Ready(Err(RequestError::ConnectionClosed))
}
}
}