low-level-native-tls
[package]
name = "example-low-level-native-tls"
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" }
tokio = { version = "1", features = ["full"] }
tokio-native-tls = "0.3.1"
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-native-tls //! ``` 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::PathBuf; use tokio::net::TcpListener; use tokio_native_tls::{ native_tls::{Identity, Protocol, TlsAcceptor as NativeTlsAcceptor}, 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(|_| "example_low_level_rustls=debug".into()), ) .with(tracing_subscriber::fmt::layer()) .init(); let tls_acceptor = native_tls_acceptor( 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(tls_acceptor); 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 native_tls_acceptor(key_file: PathBuf, cert_file: PathBuf) -> NativeTlsAcceptor { let key_pem = std::fs::read_to_string(&key_file).unwrap(); let cert_pem = std::fs::read_to_string(&cert_file).unwrap(); let id = Identity::from_pkcs8(cert_pem.as_bytes(), key_pem.as_bytes()).unwrap(); NativeTlsAcceptor::builder(id) // let's be modern .min_protocol_version(Some(Protocol::Tlsv12)) .build() .unwrap() }
self_signed_certs/cert.pem self_signed_certs/key.pem