use std::{
collections::HashMap,
panic::AssertUnwindSafe,
pin::Pin,
sync::Arc,
task::{Context, Poll},
};
use bytes::Bytes;
use futures::FutureExt;
use http_body::{Body, Frame};
use squiche::h3::{Header, NameValue};
pub use crate::h3::common::H3Error;
use crate::{
app::{QuicScionApplication, Wakeups},
h3::common::{
H3_INTERNAL_ERROR, H3App, ReadState, StreamState,
headers::headers_to_map,
read::{ConnReader, StreamReader, on_data, on_finished, on_reset, wake_writable},
write::{pump_body, send_data, send_headers},
},
quic::connection::{ConnectionHandle, QuicScionConn, WeakConnectionHandle},
};
pub trait HttpService {
type Body: Body;
type ResponseBody: Body;
fn call(
&self,
req: http::Request<Self::Body>,
) -> impl std::future::Future<Output = http::Response<Self::ResponseBody>> + Send;
}
pub struct Http3Server<S: HttpService> {
h3: Option<squiche::h3::Connection>,
service: Arc<S>,
self_handle: Option<WeakConnectionHandle<Http3Server<S>>>,
streams: HashMap<u64, StreamState>,
}
impl<S> QuicScionApplication for Http3Server<S>
where
S: HttpService<Body = H3RequestBody> + Send + Sync + 'static,
S::ResponseBody: Send + 'static,
<S::ResponseBody as Body>::Data: Send,
<S::ResponseBody as Body>::Error: Send,
{
type Config = Http3ServerConfig<S>;
fn on_established(conn: &mut squiche::Connection, config: &Self::Config) -> Self {
let mut app = Http3Server {
h3: None,
service: config.service.clone(),
self_handle: None,
streams: HashMap::new(),
};
if conn.application_proto() != b"h3" {
tracing::warn!(
alpn = ?conn.application_proto(),
"connection ALPN is not h3; closing"
);
let _ = conn.close(true, H3_INTERNAL_ERROR, b"expected h3 alpn");
return app;
}
match squiche::h3::Config::new()
.and_then(|h3_config| squiche::h3::Connection::with_transport(conn, &h3_config))
{
Ok(h3) => app.h3 = Some(h3),
Err(err) => {
tracing::warn!(?err, "failed to create h3 connection; closing");
let _ = conn.close(true, H3_INTERNAL_ERROR, b"h3 init failed");
}
}
app
}
fn bind(&mut self, handle: &ConnectionHandle<Self>) {
self.self_handle = Some(handle.downgrade());
}
fn update(&mut self, conn: &mut squiche::Connection, wakeups: &mut Wakeups) {
let Http3Server {
h3,
service,
self_handle,
streams,
} = self;
let Some(h3) = h3.as_mut() else {
return;
};
loop {
match h3.poll(conn) {
Ok((stream_id, squiche::h3::Event::Headers { list, more_frames })) => {
if streams.contains_key(&stream_id) {
let st = streams.entry(stream_id).or_default();
st.read_state = ReadState::Trailers(headers_to_map(&list));
if let Some(w) = st.read_waker.take() {
wakeups.schedule(w);
}
} else if let Some(reader_handle) = self_handle.clone() {
dispatch_request::<S>(
stream_id,
list,
more_frames,
service,
reader_handle,
streams,
);
}
}
Ok((stream_id, squiche::h3::Event::Data)) => on_data(streams, stream_id, wakeups),
Ok((stream_id, squiche::h3::Event::Finished)) => {
on_finished(streams, stream_id, wakeups)
}
Ok((stream_id, squiche::h3::Event::Reset(code))) => {
on_reset(streams, stream_id, code, wakeups)
}
Ok((_, squiche::h3::Event::GoAway)) => {}
Ok((_, squiche::h3::Event::PriorityUpdate)) => {}
Err(squiche::h3::Error::Done) => break,
Err(err) => {
tracing::warn!(?err, "error polling h3 connection");
break;
}
}
}
wake_writable(conn, streams, wakeups);
}
fn on_closed(&mut self, wakeups: &mut Wakeups) {
for st in self.streams.values_mut() {
st.read_state = ReadState::Reset(0);
if let Some(w) = st.read_waker.take() {
wakeups.schedule(w);
}
if let Some(w) = st.write_waker.take() {
wakeups.schedule(w);
}
}
}
}
impl<S> H3App for Http3Server<S>
where
S: HttpService<Body = H3RequestBody> + Send + Sync + 'static,
S::ResponseBody: Send + 'static,
<S::ResponseBody as Body>::Data: Send,
<S::ResponseBody as Body>::Error: Send,
{
fn h3_streams(
&mut self,
) -> (
Option<&mut squiche::h3::Connection>,
&mut HashMap<u64, StreamState>,
) {
(self.h3.as_mut(), &mut self.streams)
}
}
pub struct Http3ServerConfig<S> {
service: Arc<S>,
}
impl<S> Http3ServerConfig<S> {
pub fn new(service: S) -> Self {
Self {
service: Arc::new(service),
}
}
pub fn from_shared(service: Arc<S>) -> Self {
Self { service }
}
}
pub struct H3RequestBody {
reader: Arc<dyn StreamReader>,
stream_id: u64,
}
impl Body for H3RequestBody {
type Data = Bytes;
type Error = H3Error;
fn poll_frame(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
self.reader.poll_read(self.stream_id, cx)
}
}
fn dispatch_request<S>(
stream_id: u64,
list: Vec<squiche::h3::Header>,
more_frames: bool,
service: &Arc<S>,
reader_handle: WeakConnectionHandle<Http3Server<S>>,
streams: &mut HashMap<u64, StreamState>,
) where
S: HttpService<Body = H3RequestBody> + Send + Sync + 'static,
S::ResponseBody: Send + 'static,
<S::ResponseBody as Body>::Data: Send,
<S::ResponseBody as Body>::Error: Send,
{
let reader: Arc<dyn StreamReader> = Arc::new(ConnReader {
handle: reader_handle.clone(),
});
let body = H3RequestBody { reader, stream_id };
let Some(request) = build_request(&list, body) else {
tracing::warn!(
stream_id,
"failed to build request from headers; resetting stream"
);
shutdown_stream(&reader_handle, stream_id, true);
return;
};
let mut state = StreamState::default();
if !more_frames {
state.read_state = ReadState::Eof;
}
streams.insert(stream_id, state);
let service = service.clone();
tokio::spawn(async move {
let mut cleanup = StreamCleanup {
handle: reader_handle.clone(),
stream_id,
done_cleanly: false,
};
match AssertUnwindSafe(service.call(request)).catch_unwind().await {
Ok(response) => {
cleanup.done_cleanly =
serve_response::<S>(reader_handle, stream_id, response).await;
}
Err(_panic) => {
tracing::error!(stream_id, "h3 service handler panicked; replying 500");
cleanup.done_cleanly = send_error_response::<S>(&reader_handle, stream_id).await;
}
}
});
}
async fn serve_response<S>(
handle: WeakConnectionHandle<Http3Server<S>>,
stream_id: u64,
response: http::Response<S::ResponseBody>,
) -> bool
where
S: HttpService<Body = H3RequestBody> + Send + Sync + 'static,
S::ResponseBody: Send + 'static,
<S::ResponseBody as Body>::Data: Send,
<S::ResponseBody as Body>::Error: Send,
{
let (parts, body) = response.into_parts();
let headers = response_headers(&parts);
if let Err(err) = send_headers(&handle, stream_id, headers).await {
tracing::debug!(?err, stream_id, "failed to send response headers");
return false;
}
pump_body(&handle, stream_id, body).await
}
async fn send_error_response<S>(
handle: &WeakConnectionHandle<Http3Server<S>>,
stream_id: u64,
) -> bool
where
S: HttpService<Body = H3RequestBody> + Send + Sync + 'static,
S::ResponseBody: Send + 'static,
<S::ResponseBody as Body>::Data: Send,
<S::ResponseBody as Body>::Error: Send,
{
let headers = vec![Header::new(b":status", b"500")];
if let Err(err) = send_headers(handle, stream_id, headers).await {
tracing::debug!(?err, stream_id, "failed to send 500 response headers");
return false;
}
send_data(handle, stream_id, Bytes::new(), true)
.await
.is_ok()
}
struct StreamCleanup<S>
where
S: HttpService<Body = H3RequestBody> + Send + Sync + 'static,
S::ResponseBody: Send + 'static,
<S::ResponseBody as Body>::Data: Send,
<S::ResponseBody as Body>::Error: Send,
{
handle: WeakConnectionHandle<Http3Server<S>>,
stream_id: u64,
done_cleanly: bool,
}
impl<S> Drop for StreamCleanup<S>
where
S: HttpService<Body = H3RequestBody> + Send + Sync + 'static,
S::ResponseBody: Send + 'static,
<S::ResponseBody as Body>::Data: Send,
<S::ResponseBody as Body>::Error: Send,
{
fn drop(&mut self) {
shutdown_stream(&self.handle, self.stream_id, !self.done_cleanly);
}
}
fn shutdown_stream<S: HttpService>(
handle: &WeakConnectionHandle<Http3Server<S>>,
stream_id: u64,
reset_write: bool,
) {
let Some(handle) = handle.upgrade() else {
return;
};
let mut guard = handle.lock_recovering();
let QuicScionConn { inner, app, .. } = &mut *guard;
let _ = inner.stream_shutdown(stream_id, squiche::Shutdown::Read, 0);
if reset_write {
let _ = inner.stream_shutdown(stream_id, squiche::Shutdown::Write, H3_INTERNAL_ERROR);
}
app.streams.remove(&stream_id);
drop(guard);
handle.notify();
}
fn response_headers(parts: &http::response::Parts) -> Vec<Header> {
let mut headers = vec![Header::new(b":status", parts.status.as_str().as_bytes())];
for (name, value) in parts.headers.iter() {
headers.push(Header::new(name.as_str().as_bytes(), value.as_bytes()));
}
headers
}
fn build_request(
list: &[squiche::h3::Header],
body: H3RequestBody,
) -> Option<http::Request<H3RequestBody>> {
let mut method: Option<http::Method> = None;
let mut authority: Option<Vec<u8>> = None;
let mut path: Option<Vec<u8>> = None;
let mut scheme: Option<Vec<u8>> = None;
let mut headers = http::HeaderMap::new();
for header in list {
match header.name() {
b":method" => method = http::Method::from_bytes(header.value()).ok(),
b":authority" => authority = Some(header.value().to_vec()),
b":path" => path = Some(header.value().to_vec()),
b":scheme" => scheme = Some(header.value().to_vec()),
name if name.starts_with(b":") => {}
name => {
if let (Ok(name), Ok(value)) = (
http::HeaderName::from_bytes(name),
http::HeaderValue::from_bytes(header.value()),
) {
headers.append(name, value);
}
}
}
}
let method = method?;
let uri = if method == http::Method::CONNECT {
let authority = authority.as_deref()?;
let authority = http::uri::Authority::try_from(authority).ok()?;
let mut parts = http::uri::Parts::default();
parts.authority = Some(authority);
http::Uri::from_parts(parts).ok()?
} else {
let scheme = scheme.as_deref().unwrap_or(b"https");
let authority = authority.as_deref().unwrap_or(b"");
let path = path.as_deref().unwrap_or(b"/");
let mut raw = Vec::with_capacity(scheme.len() + 3 + authority.len() + path.len());
raw.extend_from_slice(scheme);
raw.extend_from_slice(b"://");
raw.extend_from_slice(authority);
raw.extend_from_slice(path);
http::Uri::try_from(raw).ok()?
};
let mut request = http::Request::builder()
.method(method)
.uri(uri)
.body(body)
.ok()?;
*request.headers_mut() = headers;
Some(request)
}