use std::{
io,
pin::Pin,
sync::Arc,
task::{Context, Poll},
};
use bytes::Bytes;
use squiche::h3::Header;
use tokio::io::AsyncWrite;
use super::StreamRef;
use crate::{
h3::{
client::app::Http3ClientApp,
common::{
H3_REQUEST_CANCELLED, H3Error,
headers::header_map_to_h3,
write::{send_data, send_trailers},
},
},
quic::connection::{QuicScionConn, WeakConnectionHandle},
};
pub struct RequestBodyWriter {
guard: WriteGuard,
}
impl RequestBodyWriter {
pub(crate) fn new(guard: WriteGuard) -> Self {
Self { guard }
}
pub async fn write_chunk(&mut self, data: Bytes) -> Result<(), H3Error> {
send_data(&self.guard.handle(), self.guard.stream_id(), data, false).await
}
pub async fn finish(mut self) -> Result<(), H3Error> {
send_data(
&self.guard.handle(),
self.guard.stream_id(),
Bytes::new(),
true,
)
.await?;
self.guard.mark_done();
Ok(())
}
pub async fn write_trailers(mut self, trailers: http::HeaderMap) -> Result<(), H3Error> {
let h3_trailers = header_map_to_h3(&trailers);
if h3_trailers.is_empty() {
send_data(
&self.guard.handle(),
self.guard.stream_id(),
Bytes::new(),
true,
)
.await?;
} else {
send_trailers(&self.guard.handle(), self.guard.stream_id(), h3_trailers).await?;
}
self.guard.mark_done();
Ok(())
}
}
impl AsyncWrite for RequestBodyWriter {
fn poll_write(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
if buf.is_empty() {
return Poll::Ready(Ok(0));
}
let this = self.get_mut();
poll_send(&this.guard.handle(), this.guard.stream_id(), cx, buf, false)
}
fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Poll::Ready(Ok(()))
}
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
let this = self.get_mut();
match poll_send(&this.guard.handle(), this.guard.stream_id(), cx, &[], true) {
Poll::Ready(Ok(_)) => {
this.guard.mark_done();
Poll::Ready(Ok(()))
}
Poll::Ready(Err(err)) => {
this.guard.mark_done();
Poll::Ready(Err(err))
}
Poll::Pending => Poll::Pending,
}
}
}
pub(crate) struct WriteGuard {
stream_ref: Arc<StreamRef>,
done: bool,
}
impl WriteGuard {
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 mark_done(&mut self) {
self.done = true;
}
}
impl Drop for WriteGuard {
fn drop(&mut self) {
if !self.done {
shutdown_write(&self.stream_ref.handle(), self.stream_ref.stream_id());
}
}
}
fn shutdown_write(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::Write, H3_REQUEST_CANCELLED);
drop(guard);
handle.notify();
}
fn poll_send(
handle: &WeakConnectionHandle<Http3ClientApp>,
stream_id: u64,
cx: &mut Context<'_>,
buf: &[u8],
fin: bool,
) -> Poll<io::Result<usize>> {
let Some(handle) = handle.upgrade() else {
return Poll::Ready(Err(io::Error::new(
io::ErrorKind::NotConnected,
"connection closed",
)));
};
let mut guard = handle.lock();
let QuicScionConn { inner, app, .. } = &mut *guard;
let Some(h3) = app.h3.as_mut() else {
return Poll::Ready(Err(io::Error::new(
io::ErrorKind::NotConnected,
"connection closed",
)));
};
match h3.send_body(inner, stream_id, buf, fin) {
Ok(written) => {
drop(guard);
handle.notify();
Poll::Ready(Ok(written))
}
Err(squiche::h3::Error::Done) | Err(squiche::h3::Error::StreamBlocked) => {
let Some(st) = app.streams.get_mut(&stream_id) else {
return Poll::Ready(Err(io::Error::new(
io::ErrorKind::NotConnected,
"connection closed",
)));
};
st.write_waker = Some(cx.waker().clone());
drop(guard);
handle.notify();
Poll::Pending
}
Err(err) => Poll::Ready(Err(io::Error::other(format!("h3 send error: {err}")))),
}
}
pub(crate) fn request_headers(parts: &http::request::Parts) -> Vec<Header> {
let mut headers = vec![Header::new(b":method", parts.method.as_str().as_bytes())];
if parts.method == http::Method::CONNECT {
if let Some(authority) = parts.uri.authority() {
headers.push(Header::new(b":authority", authority.as_str().as_bytes()));
}
} else {
let scheme = parts.uri.scheme_str().unwrap_or("https");
headers.push(Header::new(b":scheme", scheme.as_bytes()));
if let Some(authority) = parts.uri.authority() {
headers.push(Header::new(b":authority", authority.as_str().as_bytes()));
}
let path = parts
.uri
.path_and_query()
.map(|pq| pq.as_str())
.unwrap_or("/");
headers.push(Header::new(b":path", path.as_bytes()));
}
for (name, value) in parts.headers.iter() {
headers.push(Header::new(name.as_str().as_bytes(), value.as_bytes()));
}
headers
}