use std::collections::VecDeque;
use std::io;
use std::net::{SocketAddr, ToSocketAddrs};
use std::sync::Arc;
use std::time::Duration;
use bytes::Bytes;
use foundation_core::valtron::{Ctx,
self, Pipe, PipeReceiver, PipeSender, Stream as VStream, TaskIterator,
TaskStatus, TryRecvError, TrySendError,
};
use foundation_netio::http3::connection::{error_code, H3Connection, H3Request};
use foundation_netio::http3::H3Error;
use foundation_netio::quic::{ClientConfig, QuicDriver, QuinnBidiStream, QuinnConnection};
use foundation_netio::shared::http::{
Proto, RequestDescriptor, SimpleHeader, SimpleHeaders, Status,
};
use crate::shared::transport::{
body_stream_from_pipe, head_stream_from_pipe, Transport, TransportCapabilities,
TransportError, TransportStream,
};
const POLL_DELAY: Duration = Duration::from_millis(1);
#[derive(Clone)]
pub struct H3Transport {
client_config: ClientConfig,
server_name: Option<String>,
}
impl H3Transport {
#[must_use]
pub fn new(client_config: ClientConfig) -> Self {
Self { client_config, server_name: None }
}
#[must_use]
pub fn with_server_name(mut self, name: impl Into<String>) -> Self {
self.server_name = Some(name.into());
self
}
}
impl Transport for H3Transport {
fn capabilities(&self) -> TransportCapabilities {
TransportCapabilities {
request_streaming: true,
full_duplex: true,
h2_trailers: true,
http_versions: &[Proto::HTTP30],
multiplexed: false,
}
}
fn open(&self, request: RequestDescriptor) -> Result<TransportStream, TransportError> {
let host = request.request_uri.host_str().unwrap_or_else(|| "localhost".into());
let port = request.request_uri.port_or_default();
let path = request.request_uri.path().to_string();
let scheme = request.request_uri.scheme().to_string();
let method = request.method.to_string();
let req_headers = request.headers.clone();
let addr: SocketAddr = (host.as_str(), port)
.to_socket_addrs()
.map_err(|e| TransportError::Connect(Arc::new(e)))?
.next()
.ok_or_else(|| {
TransportError::Connect(Arc::new(io::Error::new(
io::ErrorKind::AddrNotAvailable,
format!("no address for {host}:{port}"),
)))
})?;
let server_name = self.server_name.clone().unwrap_or_else(|| host.clone());
let (driver, quic_conn) =
QuicDriver::connect(addr, self.client_config.clone(), &server_name)
.map_err(|e| TransportError::Connect(Arc::new(e)))?;
let (send_tx, send_rx) = Pipe::<Bytes>::new();
let (head_tx, head_rx) = Pipe::<(Status, SimpleHeaders)>::new();
let (body_tx, body_rx) = Pipe::<Bytes>::new();
let (trailer_tx, trailer_rx) = Pipe::<SimpleHeaders>::with_depth(1);
let pump = H3Pump {
driver,
conn: H3Connection::new(quic_conn),
req: None,
phase: Phase::Setup,
pending_write: Bytes::new(),
send_rx,
head_tx,
body_tx,
trailer_tx,
authority: format!("{host}:{port}"),
scheme,
method,
path,
req_headers,
request_ended: false,
head_delivered: false,
pending_head: None,
pending_body: VecDeque::new(),
response_ended: false,
};
valtron::send(pump).map_err(|e| {
TransportError::Connect(Arc::new(io::Error::other(e.to_string())))
})?;
Ok(TransportStream {
send_body: Arc::new(send_tx),
head: head_stream_from_pipe(head_rx),
recv_body: body_stream_from_pipe(body_rx),
trailers: trailer_rx,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Phase {
Setup,
OpenRequest,
SendHeaders,
Streaming,
Done,
}
struct H3Pump {
driver: QuicDriver,
conn: H3Connection<QuinnConnection>,
req: Option<H3Request<QuinnBidiStream>>,
phase: Phase,
pending_write: Bytes,
send_rx: PipeReceiver<Bytes>,
head_tx: PipeSender<(Status, SimpleHeaders)>,
body_tx: PipeSender<Bytes>,
trailer_tx: PipeSender<SimpleHeaders>,
authority: String,
scheme: String,
method: String,
path: String,
req_headers: SimpleHeaders,
request_ended: bool,
head_delivered: bool,
pending_head: Option<(Status, SimpleHeaders)>,
pending_body: VecDeque<Bytes>,
response_ended: bool,
}
impl H3Pump {
fn request_fields(&self) -> Vec<(Bytes, Bytes)> {
let mut fields = vec![
(Bytes::from_static(b":method"), Bytes::from(self.method.clone())),
(Bytes::from_static(b":scheme"), Bytes::from(self.scheme.clone())),
(Bytes::from_static(b":authority"), Bytes::from(self.authority.clone())),
(Bytes::from_static(b":path"), Bytes::from(self.path.clone())),
];
for (name, values) in &self.req_headers {
let lowered = name.to_string().to_ascii_lowercase();
if matches!(
lowered.as_str(),
"connection" | "keep-alive" | "proxy-connection" | "transfer-encoding" | "upgrade"
) {
continue;
}
for value in values {
fields.push((Bytes::from(lowered.clone()), Bytes::from(value.clone())));
}
}
fields
}
fn flush(&mut self) -> Result<bool, H3Error> {
if self.pending_write.is_empty() {
return Ok(true);
}
let req = self.req.as_mut().expect("request open");
match req.poll_send_data(&mut self.pending_write) {
VStream::Next(Ok(())) => Ok(true),
VStream::Next(Err(e)) => Err(e),
_ => Ok(false),
}
}
fn pump_request(&mut self) -> Result<(), H3Error> {
if self.request_ended || !self.flush()? {
return Ok(());
}
loop {
match self.send_rx.try_recv() {
Ok(chunk) => {
if chunk.is_empty() {
continue;
}
self.pending_write = H3Request::<QuinnBidiStream>::encode_data(chunk);
if !self.flush()? {
return Ok(());
}
}
Err(TryRecvError::Empty) => return Ok(()),
Err(TryRecvError::Closed) => {
let req = self.req.as_mut().expect("request open");
return match req.poll_finish() {
VStream::Next(Ok(())) => {
self.request_ended = true;
Ok(())
}
VStream::Next(Err(e)) => Err(e),
_ => Ok(()),
};
}
}
}
}
fn pump_response(&mut self) -> Result<(), H3Error> {
let req = self.req.as_mut().expect("request open");
if !self.head_delivered {
let head = match self.pending_head.take() {
Some(head) => head,
None => match req.poll_headers() {
VStream::Next(Ok(fields)) => split_response_fields(&fields),
VStream::Next(Err(e)) => return Err(e),
_ => return Ok(()),
},
};
match self.head_tx.try_send(head) {
Ok(()) | Err(TrySendError::Closed(_)) => self.head_delivered = true,
Err(TrySendError::Full(head)) => {
self.pending_head = Some(head);
return Ok(());
}
}
}
while let Some(front) = self.pending_body.front().cloned() {
if self.body_tx.try_send(front).is_ok() {
self.pending_body.pop_front();
} else {
return Ok(());
}
}
if self.response_ended {
return Ok(());
}
match req.poll_body() {
VStream::Next(Ok(Some(chunk))) => {
if self.body_tx.try_send(chunk.clone()).is_err() {
self.pending_body.push_back(chunk);
}
Ok(())
}
VStream::Next(Ok(None)) => {
self.response_ended = true;
if let Some(trailers) = req.trailers() {
let mut headers = SimpleHeaders::new();
for (name, value) in trailers {
headers
.entry(SimpleHeader::from(
String::from_utf8_lossy(name).to_string(),
))
.or_default()
.push(String::from_utf8_lossy(value).to_string());
}
let _ = self.trailer_tx.try_send(headers);
}
Ok(())
}
VStream::Next(Err(e)) => Err(e),
_ => Ok(()),
}
}
fn fail(&mut self, error: &H3Error) {
tracing::debug!(error = %error, "HTTP/3 exchange failed");
if let Some(req) = self.req.as_mut() {
req.reset(error_code::H3_NO_ERROR);
}
self.head_tx.close();
self.body_tx.close();
self.trailer_tx.close();
self.phase = Phase::Done;
}
fn finish(&mut self) {
self.body_tx.close();
self.head_tx.close();
self.trailer_tx.close();
self.conn.close(error_code::H3_NO_ERROR, b"done");
self.phase = Phase::Done;
}
}
fn split_response_fields(fields: &[(Bytes, Bytes)]) -> (Status, SimpleHeaders) {
let mut status = Status::OK;
let mut headers = SimpleHeaders::new();
for (name, value) in fields {
if &name[..] == b":status" {
status = Status::from(String::from_utf8_lossy(value).to_string());
continue;
}
headers
.entry(SimpleHeader::from(String::from_utf8_lossy(name).to_string()))
.or_default()
.push(String::from_utf8_lossy(value).to_string());
}
(status, headers)
}
impl TaskIterator for H3Pump {
type Ready = ();
type Pending = ();
fn next_status(&mut self, ctx: Ctx) -> Option<TaskStatus<Self::Ready, Self::Pending>> {
if self.phase == Phase::Done {
return None;
}
let _ = self.driver.next_status(ctx);
match self.phase {
Phase::Setup => match self.conn.poll_setup() {
VStream::Next(Ok(())) => self.phase = Phase::OpenRequest,
VStream::Next(Err(e)) => {
self.fail(&e);
return None;
}
_ => return Some(TaskStatus::Delayed(POLL_DELAY)),
},
Phase::OpenRequest => match self.conn.poll_open_request() {
VStream::Next(Ok(req)) => {
let fields = self.request_fields();
let pairs: Vec<(&[u8], &[u8])> =
fields.iter().map(|(n, v)| (&n[..], &v[..])).collect();
self.pending_write = H3Request::<QuinnBidiStream>::encode_headers(&pairs);
self.req = Some(req);
self.phase = Phase::SendHeaders;
}
VStream::Next(Err(e)) => {
self.fail(&e);
return None;
}
_ => return Some(TaskStatus::Delayed(POLL_DELAY)),
},
Phase::SendHeaders => match self.flush() {
Ok(true) => self.phase = Phase::Streaming,
Ok(false) => return Some(TaskStatus::Delayed(POLL_DELAY)),
Err(e) => {
self.fail(&e);
return None;
}
},
Phase::Streaming => {
if let Err(e) = self.pump_request() {
self.fail(&e);
return None;
}
if let Err(e) = self.pump_response() {
self.fail(&e);
return None;
}
if self.response_ended && self.pending_body.is_empty() && self.head_delivered {
self.finish();
return None;
}
}
Phase::Done => return None,
}
Some(TaskStatus::Delayed(POLL_DELAY))
}
}