use std::future::Future;
use std::io;
use std::sync::Arc;
use std::task::{Context, Poll};
use bytes::Bytes;
use foundation_core::valtron::{Pipe, Stream as VStream};
use foundation_http::native::serve::{BoxFuture, H3Serve};
use foundation_http::shared::context::ContextBag;
use foundation_netio::http3::connection::{H3Error, H3Request};
use foundation_netio::http3::types::{request_from_fields, response_to_fields};
use foundation_netio::netcap::context::ConnectionContext;
use foundation_netio::quic::QuinnBidiStream;
use foundation_netio::shared::client::body_reader::AsyncSendSafeBody;
use foundation_netio::shared::http::{SendSafeBody, SimpleOutgoingResponse, Status};
use futures::StreamExt;
use crate::shared::context::{Ctx, Spec};
use crate::shared::protocol::{HandlerExchange, ProtocolHandler};
use crate::shared::router::dispatch::{
self, allowed_methods, blank_response, build_ctx, capabilities_for, extract_path,
http_version_not_supported, method_not_allowed, request_from_header, run_unary,
status_only, unsupported_media_type, write_error,
};
use crate::shared::router::{ConnectRpcHandler, HandlerKind};
use crate::shared::transport::capabilities::{requirements, ProtocolKind};
use crate::shared::transport::check_compatible;
pub struct ConnectRpcServeH3 {
handler: Arc<ConnectRpcHandler>,
}
impl ConnectRpcServeH3 {
#[must_use]
pub fn new(handler: ConnectRpcHandler) -> Self {
Self {
handler: Arc::new(handler),
}
}
}
impl H3Serve for ConnectRpcServeH3 {
fn serve_h3(
&self,
bag: Arc<ContextBag>,
connection: Arc<ConnectionContext>,
request: H3Request<QuinnBidiStream>,
) -> BoxFuture<'static, io::Result<()>> {
let handler = self.handler.clone();
Box::pin(dispatch_h3(handler, bag, connection, request))
}
}
pub async fn dispatch_h3(
handler: Arc<ConnectRpcHandler>,
bag: Arc<ContextBag>,
connection: Arc<ConnectionContext>,
mut req: H3Request<QuinnBidiStream>,
) -> io::Result<()> {
let fields = poll_h3(|| req.poll_headers()).await?;
let header = request_from_fields(&fields, connection)
.map_err(|e| io::Error::other(e.to_string()))?;
let request = request_from_header(&header);
let path = extract_path(&request.request_url.url);
let router = handler.router();
let Some(entry) = router.lookup(&path) else {
let resp = status_only(&request, Status::NotFound);
send_h3_response(&mut req, resp).await?;
return Ok(());
};
let allowed = allowed_methods(entry);
if !allowed.iter().any(|m| *m == request.method) {
let resp = method_not_allowed(&request, &allowed);
send_h3_response(&mut req, resp).await?;
return Ok(());
}
let Some(protocol) = entry
.protocol_handlers
.iter()
.find(|p| p.can_handle(&request))
else {
let resp = unsupported_media_type(&request, entry);
send_h3_response(&mut req, resp).await?;
return Ok(());
};
let protocol = protocol.as_ref();
let Some(codec_name) = protocol.codec_name(&request) else {
let resp = unsupported_media_type(&request, entry);
send_h3_response(&mut req, resp).await?;
return Ok(());
};
if !entry.codec_meta.has_codec(&codec_name) {
let resp = unsupported_media_type(&request, entry);
send_h3_response(&mut req, resp).await?;
return Ok(());
}
let caps = capabilities_for(&request.proto);
let reqs = requirements(protocol.kind(), entry.spec.stream_type);
if check_compatible(&reqs, &caps).is_err() {
let resp = http_version_not_supported(&request);
send_h3_response(&mut req, resp).await?;
return Ok(());
}
if entry.options.require_connect_protocol_header && protocol.kind() == ProtocolKind::Connect {
if let Err(e) = dispatch::require_connect_version(&request) {
let mut response = blank_response(&request);
write_error(&mut response, &request, &e);
send_h3_response(&mut req, response).await?;
return Ok(());
}
}
let compression = entry
.options
.compression
.as_ref()
.unwrap_or_else(|| router.global_compression());
let spec = entry.spec.clone();
let ctx = build_ctx(bag, &request, spec.clone(), protocol);
match &entry.handler {
HandlerKind::Unary(handler_fn) => {
let body_bytes = collect_h3_body(&mut req).await?;
let mut request = request;
request.body = Some(send_safe_bytes(body_bytes));
let response = run_unary(
handler_fn.clone(),
protocol,
request,
codec_name,
compression,
ctx,
entry.options.limits.read_max_bytes,
)
.await;
send_h3_response(&mut req, response).await
}
HandlerKind::ServerStream(handler)
| HandlerKind::ClientStream(handler)
| HandlerKind::BidiStream(handler) => {
run_streaming_h3(
handler.clone(),
protocol,
request,
spec,
codec_name,
compression,
ctx,
entry.options.pipe_depth,
req,
)
.await
}
}
}
#[allow(clippy::too_many_arguments)]
async fn run_streaming_h3(
handler: Arc<dyn crate::shared::router::erased::ErasedStreamHandler>,
protocol: &dyn ProtocolHandler,
request: foundation_netio::shared::http::SimpleIncomingRequest,
spec: Spec,
codec_name: String,
compression: &crate::shared::compression::CompressionRegistry,
ctx: Ctx,
pipe_depth: usize,
mut req: H3Request<QuinnBidiStream>,
) -> io::Result<()> {
let (req_tx, req_rx) = Pipe::<Bytes>::with_depth(pipe_depth);
let (resp_tx, resp_rx) = Pipe::<Bytes>::with_depth(pipe_depth);
loop {
match poll_h3(|| req.poll_body()).await {
Ok(Some(chunk)) => {
let _ = req_tx.send(chunk).await;
}
Ok(None) => break,
Err(_) => break,
}
}
req_tx.close();
let exchange = match protocol.new_conn(&request, spec, req_rx, resp_tx, compression) {
Ok(exchange) => exchange,
Err(e) => {
let mut response = blank_response(&request);
write_error(&mut response, &request, &e);
return send_h3_response(&mut req, response).await;
}
};
let HandlerExchange {
conn,
reader_task,
writer_task,
} = exchange;
let content_type = protocol.streaming_response_content_type(&request, &codec_name);
let pump = async {
let mut opened = false;
while let Some(chunk) = resp_rx.receive().await {
if !opened {
opened = true;
let headers = vec![(
Bytes::from_static(b"content-type"),
Bytes::from(content_type.clone()),
)];
let mut header_frame = H3Request::<QuinnBidiStream>::encode_headers(&headers);
poll_h3(|| req.poll_send_headers(&mut header_frame)).await?;
}
let mut data_frame = H3Request::<QuinnBidiStream>::encode_data(chunk);
poll_h3(|| req.poll_send_data(&mut data_frame)).await?;
}
Ok::<bool, io::Error>(opened)
};
let handler_fut = handler.handle(ctx, &codec_name, conn);
let (reader_res, writer_res, handler_res, pumped) =
futures::join!(reader_task, writer_task, handler_fut, pump);
if pumped? {
poll_h3_finish(&mut req).await?;
return Ok(());
}
let mut response = blank_response(&request);
if let Some(e) = handler_res.err().or(writer_res.err()).or(reader_res.err()) {
write_error(&mut response, &request, &e);
}
send_h3_response(&mut req, response).await
}
fn send_safe_bytes(b: Bytes) -> SendSafeBody {
SendSafeBody::Bytes(b.to_vec())
}
fn poll_h3<T>(
mut f: impl FnMut() -> VStream<Result<T, H3Error>, ()>,
) -> impl Future<Output = io::Result<T>> {
std::future::poll_fn(move |_cx: &mut Context<'_>| match f() {
VStream::Next(Ok(v)) => Poll::Ready(Ok(v)),
VStream::Next(Err(e)) => Poll::Ready(Err(io::Error::other(e.to_string()))),
_ => Poll::Pending,
})
}
async fn collect_h3_body(req: &mut H3Request<QuinnBidiStream>) -> io::Result<Bytes> {
let mut body = Bytes::new();
loop {
match poll_h3(|| req.poll_body()).await? {
Some(chunk) => {
if body.is_empty() {
body = chunk;
} else {
let mut merged = bytes::BytesMut::with_capacity(body.len() + chunk.len());
merged.extend_from_slice(&body);
merged.extend_from_slice(&chunk);
body = merged.freeze();
}
}
None => return Ok(body),
}
}
}
async fn send_h3_response(
req: &mut H3Request<QuinnBidiStream>,
mut response: SimpleOutgoingResponse,
) -> io::Result<()> {
let response_fields = response_to_fields(&response);
let body = response.body.take();
let mut header_frame = H3Request::<QuinnBidiStream>::encode_headers(&response_fields);
poll_h3(|| req.poll_send_headers(&mut header_frame)).await?;
let stream = body
.filter(|b| !matches!(b, SendSafeBody::None))
.map(AsyncSendSafeBody::from);
if let Some(mut stream) = stream {
while let Some(chunk) = stream.next().await {
let chunk = chunk.map_err(|e| io::Error::other(e.to_string()))?;
let mut data_frame = H3Request::<QuinnBidiStream>::encode_data(Bytes::from(chunk));
poll_h3(|| req.poll_send_data(&mut data_frame)).await?;
}
}
poll_h3_finish(req).await
}
fn poll_h3_finish(
req: &mut H3Request<QuinnBidiStream>,
) -> impl Future<Output = io::Result<()>> + '_ {
std::future::poll_fn(move |_cx: &mut Context<'_>| match req.poll_finish() {
VStream::Next(Ok(())) => Poll::Ready(Ok(())),
VStream::Next(Err(e)) => Poll::Ready(Err(io::Error::other(e.to_string()))),
_ => Poll::Pending,
})
}