🐙 tako
Transports

HTTP/3

Serve HTTP/3 over QUIC with quinn — tokio-only, TLS mandatory, configurable congestion control and stream caps.

HTTP/3

HTTP/3 runs over QUIC (UDP) using quinn for transport and the h3 crate for the protocol. It is gated behind the http3 feature. The same Router you use for HTTP/1.1 and HTTP/2 drives HTTP/3 unchanged.

HTTP/3 is tokio-only — it is not available on the Compio runtime. TLS is mandatory for QUIC, so a TlsCert is always required. Enabling http3 also enables the webtransport feature (see WebTransport).

Quickstart

QUIC binds a UDP socket, not a TcpListener, so the address is passed at spawn time. tako::serve_h3 boots a single HTTP/3 server until the process exits.

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::serve_h3(router, "[::]:4433", Some("cert.pem"), Some("key.pem")).await;
  Ok(())
}

The certs and key arguments are Option<&str> paths to PEM files; they default to cert.pem / key.pem when None. Test with a recent curl:

curl --http3 -k https://localhost:4433/
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::serve_h3(router, "[::]:4433", Some("cert.pem"), Some("key.pem")).await;

  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(())
}

spawn_h3 panics if the builder was not given a TlsCert — QUIC cannot run without one. The PEM-paths fast path is used directly; Der, Resolver, and mTLS variants flow through the shared rustls-config helper.

Tuning QUIC

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

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

HTTP/3 installs the ring rustls CryptoProvider during its QUIC bootstrap. If your process also serves plain TLS and you want a specific provider, pin it at startup with rustls::crypto::aws_lc_rs::default_provider().install_default() before constructing any server — once a provider is installed, Tako uses it as-is for key loading.

  • HTTP — HTTP/1.1 and HTTP/2.
  • WebTransport — raw QUIC sessions, enabled alongside http3.
  • Feature flags — the http3 feature and what it pulls in.

On this page