low-level-rustls
[package]
name = "example-low-level-rustls"
version = "0.1.0"
edition = "2021"
publish = false
[dependencies]
axum = { path = "../../axum" }
futures-util = { version = "0.3", default-features = false }
hyper = { version = "1.0.0", features = ["full"] }
hyper-util = { version = "0.1", features = ["http2"] }
tokio = { version = "1", features = ["full"] }
tokio-rustls = "0.26"
tower-service = "0.3.2"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
//! Run with //! //! ```not_rust //! cargo run -p example-low-level-rustls //! ``` use axum::{extract::Request, routing::get, Router}; use futures_util::pin_mut; use hyper::body::Incoming; use hyper_util::rt::{TokioExecutor, TokioIo}; use std::{ path::{Path, PathBuf}, sync::Arc, }; use tokio::net::TcpListener; use tokio_rustls::{ rustls::pki_types::{pem::PemObject, CertificateDer, PrivateKeyDer}, rustls::ServerConfig, TlsAcceptor, }; use tower_service::Service; use tracing::{error, info, warn}; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; #[tokio::main] async fn main() { tracing_subscriber::registry() .with( tracing_subscriber::EnvFilter::try_from_default_env() .unwrap_or_else(|_| format!("{}=debug", env!("CARGO_CRATE_NAME")).into()), ) .with(tracing_subscriber::fmt::layer()) .init(); let rustls_config = rustls_server_config( PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("self_signed_certs") .join("key.pem"), PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("self_signed_certs") .join("cert.pem"), ); let tls_acceptor = TlsAcceptor::from(rustls_config); let bind = "[::1]:3000"; let tcp_listener = TcpListener::bind(bind).await.unwrap(); info!("HTTPS server listening on {bind}. To contact curl -k https://localhost:3000"); let app = Router::new().route("/", get(handler)); pin_mut!(tcp_listener); loop { let tower_service = app.clone(); let tls_acceptor = tls_acceptor.clone(); // Wait for new tcp connection let (cnx, addr) = tcp_listener.accept().await.unwrap(); tokio::spawn(async move { // Wait for tls handshake to happen let Ok(stream) = tls_acceptor.accept(cnx).await else { error!("error during tls handshake connection from {}", addr); return; }; // Hyper has its own `AsyncRead` and `AsyncWrite` traits and doesn't use tokio. // `TokioIo` converts between them. let stream = TokioIo::new(stream); // Hyper also has its own `Service` trait and doesn't use tower. We can use // `hyper::service::service_fn` to create a hyper `Service` that calls our app through // `tower::Service::call`. let hyper_service = hyper::service::service_fn(move |request: Request<Incoming>| { // We have to clone `tower_service` because hyper's `Service` uses `&self` whereas // tower's `Service` requires `&mut self`. // // We don't need to call `poll_ready` since `Router` is always ready. tower_service.clone().call(request) }); let ret = hyper_util::server::conn::auto::Builder::new(TokioExecutor::new()) .serve_connection_with_upgrades(stream, hyper_service) .await; if let Err(err) = ret { warn!("error serving connection from {}: {}", addr, err); } }); } } async fn handler() -> &'static str { "Hello, World!" } fn rustls_server_config(key: impl AsRef<Path>, cert: impl AsRef<Path>) -> Arc<ServerConfig> { let key = PrivateKeyDer::from_pem_file(key).unwrap(); let certs = CertificateDer::pem_file_iter(cert) .unwrap() .map(|cert| cert.unwrap()) .collect(); let mut config = ServerConfig::builder() .with_no_client_auth() .with_single_cert(certs, key) .expect("bad certificate/key"); config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; Arc::new(config) }
Additional files:
self_signed_certs/cert.pem self_signed_certs/key.pem