🐙 tako
Transports

HTTP/3

Serve HTTP/3 over QUIC on Tokio (quinn) or Compio (compio-quic) with mandatory TLS, congestion control, and stream caps.

HTTP/3 runs over QUIC (UDP) with the h3 crate for the protocol: quinn drives QUIC on Tokio and compio-quic on Compio. It is gated behind the http3 feature. The same Router you use for HTTP/1.1 and HTTP/2 drives HTTP/3 unchanged, and handlers see a ConnInfo whose transport is Http3.

TLS is mandatory for QUIC, so a TlsCert is always required. On Compio a request body is bound to the runtime thread that received it, like every compio task; moving it to another thread panics. Enable webtransport to also accept WebTransport sessions on the same server.

Quickstart

QUIC binds a UDP socket, not a TcpListener, so the address is passed at spawn time. spawn_h3 returns a handle for startup errors and shutdown; try_spawn_h3 binds before returning, so certificate and bind errors surface immediately.

use anyhow::Result;
use tako::Method;
use tako::responder::Responder;
use tako::router::Router;

async fn hello() -> impl Responder {
  "Hello, HTTP/3 World!".into_response()
}

#[tokio::main]
async fn main() -> Result<()> {
  let mut router = Router::new();
  router.route(Method::GET, "/", hello);

  tako::Server::builder()
    .tls(tako::TlsCert::pem_paths("cert.pem", "key.pem"))
    .build().spawn_h3("[::]:4433", router).result().await?;
  Ok(())
}
use tako::router::Router;
use tako::types::BoxError;
use tako::{CompioServer, TlsCert};

#[compio::main]
async fn main() -> Result<(), BoxError> {
  let mut router = Router::new();
  router.get("/", || async { "Hello, HTTP/3 World!" });

  let handle = CompioServer::builder()
    .tls(TlsCert::pem_paths("cert.pem", "key.pem"))
    .build()
    .try_spawn_h3("[::]:4433", router)?;
  handle.result().await?;
  Ok(())
}

Test with a recent curl:

curl --http3 -k https://localhost:4433/
examples/hello-world-http3/src/main.rs
use anyhow::Result;
use tako::Method;
use tako::responder::Responder;
use tako::router::Router;

async fn hello_world() -> impl Responder {
  "Hello, HTTP/3 World!".into_response()
}

async fn json_example() -> impl Responder {
  r#"{"message": "Hello from HTTP/3!", "protocol": "h3"}"#.into_response()
}

#[tokio::main]
async fn main() -> Result<()> {
  // HTTP/3 requires TLS certificates
  // You can generate self-signed certificates for testing with:
  // openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes -subj "/CN=localhost"

  let mut router = Router::new();
  router.route(Method::GET, "/", hello_world);
  router.route(Method::GET, "/json", json_example);

  println!("Starting HTTP/3 server on [::]:4433");
  println!("Test with: curl --http3 -k https://localhost:4433/");

  tako::Server::builder()
    .tls(tako::TlsCert::pem_paths("cert.pem", "key.pem"))
    .build()
    .spawn_h3("[::]:4433", router)
    .result()
    .await
    .expect("HTTP/3 server failed");

  Ok(())
}

The Server builder

For graceful drain and an owned shutdown handle, build a Server, attach a TlsCert, and call spawn_h3 with the bind address. It returns the same ServerHandle as every other transport.

use tako::{Server, TlsCert};
use tako::router::Router;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
  let mut router = Router::new();
  router.route(tako::Method::GET, "/", || async { "Hello over HTTP/3" });

  let server = Server::builder()
    .tls(TlsCert::pem_paths("cert.pem", "key.pem"))
    .build();

  let handle = server.spawn_h3("[::]:4433", router);
  handle.join().await;
  Ok(())
}

Without a TlsCert, spawn_h3 reports the error through result() and try_spawn_h3 returns it immediately — QUIC cannot run without TLS. Every TlsCert variant (PEM paths, DER, Resolver, mTLS) flows through the same rustls-config helper as HTTPS.

Tuning QUIC

HTTP/3 knobs live on ServerConfig alongside the HTTP/1 and HTTP/2 settings and apply on both runtimes:

FieldPurpose
h3_max_concurrent_bidi_streamsCap on client-initiated bidirectional streams (default 100).
h3_max_concurrent_uni_streamsCap on client-initiated unidirectional streams (default 8).
h3_max_idle_timeoutIdle timeout with no QUIC packets either direction (default 30 s).
h3_congestionCongestion controller: Cubic (default), NewReno, or Bbr.
h3_enable_datagramsEnable QUIC datagrams (RFC 9221) on the connection.
h3_use_retryIssue a QUIC Retry packet to validate source addresses (anti-amplification).
h3_goaway_gracePer-connection grace for in-flight streams after GOAWAY.
use tako::{Server, ServerConfig, TlsCert};
use tako::router::Router;
use tako_rs_server::H3Congestion;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
  let mut router = Router::new();
  router.route(tako::Method::GET, "/", || async { "h3 tuned" });

  let server = Server::builder()
    .tls(TlsCert::pem_paths("cert.pem", "key.pem"))
    .config(ServerConfig {
      h3_congestion: H3Congestion::Bbr,
      h3_use_retry: true,
      h3_enable_datagrams: true,
      ..ServerConfig::default()
    })
    .build();

  let handle = server.spawn_h3("[::]:4433", router);
  handle.join().await;
  Ok(())
}

ServerConfig is re-exported as tako::ServerConfig; the H3Congestion enum lives in tako-rs-server, so import it from there.

The effective per-connection GOAWAY grace at runtime is min(h3_goaway_grace, drain_timeout) — a long per-connection grace cannot push the total shutdown past the global drain budget.

h3_goaway_grace larger than drain_timeout is a no-op beyond the global ceiling. Raise drain_timeout too if you need a longer overall drain window.

Certificate provider note

With http3, rustls is compiled with two crypto providers: aws-lc-rs (its default) and ring (pulled in by the QUIC stack). Tako installs aws-lc-rs as the process-wide CryptoProvider when none is installed yet. To use a different one, install it at startup — for example rustls::crypto::ring::default_provider().install_default() — before constructing any server; once a provider is installed, Tako uses it as-is.

  • HTTP — HTTP/1.1 and HTTP/2.
  • WebTransport — browser WebTransport sessions on the HTTP/3 server, behind webtransport.
  • Feature flags — the http3 feature and what it pulls in.

Last updated on

On this page