2 unstable releases
new 0.1.0 | Feb 21, 2025 |
---|---|
0.0.0 | Aug 21, 2023 |
#185 in Network programming
69 downloads per month
16MB
472K
SLoC
Tokio Quiche
Bridging the gap between quiche and tokio.
tokio-quiche connects quiche::Connections and
quiche::h3::Connections to tokio's event loop. Users have the
choice between implementing their own, custom ApplicationOverQuic
or using the ready-made H3Driver
for HTTP/3 clients and servers.
Starting an HTTP/3 Server
A server listens on a UDP socket for QUIC connections and spawns a new tokio task to handle each individual connection.
use foundations::telemetry::log;
use futures::{SinkExt as _, StreamExt as _};
use tokio_quiche::buf_factory::BufFactory;
use tokio_quiche::http3::driver::{H3Event, IncomingH3Headers, OutboundFrame, ServerH3Event};
use tokio_quiche::http3::settings::Http3Settings;
use tokio_quiche::listen;
use tokio_quiche::metrics::DefaultMetrics;
use tokio_quiche::quic::SimpleConnectionIdGenerator;
use tokio_quiche::quiche::h3;
use tokio_quiche::{ConnectionParams, ServerH3Controller, ServerH3Driver};
let socket = tokio::net::UdpSocket::bind("0.0.0.0:4043").await?;
let mut listeners = listen(
[socket],
ConnectionParams::new_server(
Default::default(),
tokio_quiche::settings::TlsCertificatePaths {
cert: "/path/to/cert.pem",
private_key: "/path/to/key.pem",
kind: tokio_quiche::settings::CertificateKind::X509,
},
Default::default(),
),
SimpleConnectionIdGenerator,
DefaultMetrics,
)?;
let accept_stream = &mut listeners[0];
while let Some(conn) = accept_stream.next().await {
let (driver, controller) = ServerH3Driver::new(Http3Settings::default());
conn?.start(driver);
tokio::spawn(handle_connection(controller));
}
async fn handle_connection(mut controller: ServerH3Controller) {
while let Some(ServerH3Event::Core(event)) = controller.event_receiver_mut().recv().await {
match event {
H3Event::IncomingHeaders(IncomingH3Headers {
mut send, headers, ..
}) => {
log::info!("incomming headers"; "headers" => ?headers);
send.send(OutboundFrame::Headers(vec![h3::Header::new(
b":status", b"200",
)]))
.await
.unwrap();
send.send(OutboundFrame::body(
BufFactory::buf_from_slice(b"hello from TQ!"),
true,
))
.await
.unwrap();
}
event => {
log::info!("event: {event:?}");
}
}
}
}
Sending an HTTP/3 request
use foundations::telemetry::log;
use tokio_quiche::http3::driver::{ClientH3Event, H3Event, InboundFrame, IncomingH3Headers};
use tokio_quiche::quiche::h3;
let socket = tokio::net::UdpSocket::bind("0.0.0.0:0").await?;
socket.connect("127.0.0.1:4043").await?;
let (_, mut controller) = tokio_quiche::quic::connect(socket, None).await?;
controller
.request_sender()
.send(tokio_quiche::http3::driver::NewClientRequest {
request_id: 0,
headers: vec![h3::Header::new(b":method", b"GET")],
body_writer: None,
})
.unwrap();
while let Some(event) = controller.event_receiver_mut().recv().await {
match event {
ClientH3Event::Core(H3Event::IncomingHeaders(IncomingH3Headers {
stream_id,
headers,
mut recv,
..
})) => {
log::info!("incomming headers"; "stream_id" => stream_id, "headers" => ?headers);
'body: while let Some(frame) = recv.recv().await {
match frame {
InboundFrame::Body(pooled, fin) => {
log::info!("inbound body: {:?}", std::str::from_utf8(&pooled);
"fin" => fin,
"len" => pooled.len()
);
if fin {
log::info!("received full body, exiting");
break 'body;
}
}
InboundFrame::Datagram(pooled) => {
log::info!("inbound datagram"; "len" => pooled.len());
}
}
}
}
ClientH3Event::Core(H3Event::BodyBytesReceived { fin: true, .. }) => {
log::info!("fin received");
break;
}
ClientH3Event::Core(event) => log::info!("received event: {event:?}"),
ClientH3Event::NewOutboundRequest {
stream_id,
request_id,
} => log::info!(
"sending outbound request";
"stream_id" => stream_id,
"request_id" => request_id
),
}
}
Note: Omited in these two examples are is the use of stream_id
to track
multiplexed requests within the same connection.
Feature Flags
tokio-quiche supports a number of feature flags to enable experimental features, performance enhancements, and additional telemetry. By default, no feature flags are enabled.
rpk
: Support for raw public keys (RPK) in QUIC handshakes (via [boring]).capture_keylogs
: OptionalSSLKEYLOGFILE
capturing for QUIC connections.gcongestion
: Replace quiche's original congestion control implementation with one adapted from google/quiche (via quiche-mallard).zero-copy
: Use zero-copy sends with quiche-mallard (impliesgcongestion
).perf-quic-listener-metrics
: Extra telemetry for QUIC handshake durations, including protocol overhead and network delays.tokio-task-metrics
: Scheduling & poll duration histograms for tokio tasks.
Server usage architecture
Client usage architecture
Dependencies
~47–64MB
~1.5M SLoC