From 998ef8dc894ddd5b34c7c5afccea6040526e7e10 Mon Sep 17 00:00:00 2001 From: PGIII Date: Mon, 11 Sep 2023 07:55:11 -0400 Subject: [PATCH] Add tls-graceful-shutdown example (#2208) --- examples/tls-graceful-shutdown/Cargo.toml | 13 ++ .../self_signed_certs/cert.pem | 22 +++ .../self_signed_certs/key.pem | 28 ++++ examples/tls-graceful-shutdown/src/main.rs | 139 ++++++++++++++++++ 4 files changed, 202 insertions(+) create mode 100644 examples/tls-graceful-shutdown/Cargo.toml create mode 100644 examples/tls-graceful-shutdown/self_signed_certs/cert.pem create mode 100644 examples/tls-graceful-shutdown/self_signed_certs/key.pem create mode 100644 examples/tls-graceful-shutdown/src/main.rs diff --git a/examples/tls-graceful-shutdown/Cargo.toml b/examples/tls-graceful-shutdown/Cargo.toml new file mode 100644 index 0000000000..7b0169ba8f --- /dev/null +++ b/examples/tls-graceful-shutdown/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "example-tls-graceful-shutdown" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +axum = { path = "../../axum" } +axum-server = { version = "0.3", features = ["tls-rustls"] } +hyper = { version = "0.14", features = ["full"] } +tokio = { version = "1", features = ["full"] } +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } diff --git a/examples/tls-graceful-shutdown/self_signed_certs/cert.pem b/examples/tls-graceful-shutdown/self_signed_certs/cert.pem new file mode 100644 index 0000000000..656aa88055 --- /dev/null +++ b/examples/tls-graceful-shutdown/self_signed_certs/cert.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDkzCCAnugAwIBAgIUXVYkRCrM/ge03DVymDtXCuybp7gwDQYJKoZIhvcNAQEL +BQAwWTELMAkGA1UEBhMCVVMxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0MB4X +DTIxMDczMTE0MjIxMloXDTIyMDczMTE0MjIxMlowWTELMAkGA1UEBhMCVVMxEzAR +BgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5 +IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEA02V5ZjmqLB/VQwTarrz/35qsa83L+DbAoa0001+jVmmC+G9Nufi0 +daroFWj/Uicv2fZWETU8JoZKUrX4BK9og5cg5rln/CtBRWCUYIwRgY9R/CdBGPn4 +kp+XkSJaCw74ZIyLy/Zfux6h8ES1m9YRnBza+s7U+ImRBRf4MRPtXQ3/mqJxAZYq +dOnKnvssRyD2qutgVTAxwMUvJWIivRhRYDj7WOpS4CEEeQxP1iH1/T5P7FdtTGdT +bVBABCA8JhL96uFGPpOYHcM/7R5EIA3yZ5FNg931QzoDITjtXGtQ6y9/l/IYkWm6 +J67RWcN0IoTsZhz0WNU4gAeslVtJLofn8QIDAQABo1MwUTAdBgNVHQ4EFgQUzFnK +NfS4LAYuKeWwHbzooER0yZ0wHwYDVR0jBBgwFoAUzFnKNfS4LAYuKeWwHbzooER0 +yZ0wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAk4O+e9jia59W +ZwetN4GU7OWcYhmOgSizRSs6u7mTfp62LDMt96WKU3THksOnZ44HnqWQxsSfdFVU +XJD12tjvVU8Z4FWzQajcHeemUYiDze8EAh6TnxnUcOrU8IcwiKGxCWRY/908jnWg ++MMscfMCMYTRdeTPqD8fGzAlUCtmyzH6KLE3s4Oo/r5+NR+Uvrwpdvb7xe0MwwO9 +Q/zR4N8ep/HwHVEObcaBofE1ssZLksX7ZgCP9wMgXRWpNAtC5EWxMbxYjBfWFH24 +fDJlBMiGJWg8HHcxK7wQhFh+fuyNzE+xEWPsI9VL1zDftd9x8/QsOagyEOnY8Vxr +AopvZ09uEQ== +-----END CERTIFICATE----- diff --git a/examples/tls-graceful-shutdown/self_signed_certs/key.pem b/examples/tls-graceful-shutdown/self_signed_certs/key.pem new file mode 100644 index 0000000000..3de14eb32f --- /dev/null +++ b/examples/tls-graceful-shutdown/self_signed_certs/key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDTZXlmOaosH9VD +BNquvP/fmqxrzcv4NsChrTTTX6NWaYL4b025+LR1qugVaP9SJy/Z9lYRNTwmhkpS +tfgEr2iDlyDmuWf8K0FFYJRgjBGBj1H8J0EY+fiSn5eRIloLDvhkjIvL9l+7HqHw +RLWb1hGcHNr6ztT4iZEFF/gxE+1dDf+aonEBlip06cqe+yxHIPaq62BVMDHAxS8l +YiK9GFFgOPtY6lLgIQR5DE/WIfX9Pk/sV21MZ1NtUEAEIDwmEv3q4UY+k5gdwz/t +HkQgDfJnkU2D3fVDOgMhOO1ca1DrL3+X8hiRabonrtFZw3QihOxmHPRY1TiAB6yV +W0kuh+fxAgMBAAECggEADltu8k1qTFLhJgsXWxTFAAe+PBgfCT2WuaRM2So+qqjB +12Of0MieYPt5hbK63HaC3nfHgqWt7yPhulpXfOH45C8IcgMXl93MMg0MJr58leMI ++2ojFrIrerHSFm5R1TxwDEwrVm/mMowzDWFtQCc6zPJ8wNn5RuP48HKfTZ3/2fjw +zEjSwPO2wFMfo1EJNTjlI303lFbdFBs67NaX6puh30M7Tn+gznHKyO5a7F57wkIt +fkgnEy/sgMedQlwX7bRpUoD6f0fZzV8Qz4cHFywtYErczZJh3VGitJoO/VCIDdty +RPXOAqVDd7EpP1UUehZlKVWZ0OZMEfRgKbRCel5abQKBgQDwgwrIQ5+BiZv6a0VT +ETeXB+hRbvBinRykNo/RvLc3j1enRh9/zO/ShadZIXgOAiM1Jnr5Gp8KkNGca6K1 +myhtad7xYPODYzNXXp6T1OPgZxHZLIYzVUj6ypXeV64Te5ZiDaJ1D49czsq+PqsQ +XRcgBJSNpFtDFiXWpjXWfx8PxwKBgQDhAnLY5Sl2eeQo+ud0MvjwftB/mN2qCzJY +5AlQpRI4ThWxJgGPuHTR29zVa5iWNYuA5LWrC1y/wx+t5HKUwq+5kxvs+npYpDJD +ZX/w0Glc6s0Jc/mFySkbw9B2LePedL7lRF5OiAyC6D106Sc9V2jlL4IflmOzt4CD +ZTNbLtC6hwKBgHfIzBXxl/9sCcMuqdg1Ovp9dbcZCaATn7ApfHd5BccmHQGyav27 +k7XF2xMJGEHhzqcqAxUNrSgV+E9vTBomrHvRvrd5Ec7eGTPqbBA0d0nMC5eeFTh7 +wV0miH20LX6Gjt9G6yJiHYSbeV5G1+vOcTYBEft5X/qJjU7aePXbWh0BAoGBAJlV +5tgCCuhvFloK6fHYzqZtdT6O+PfpW20SMXrgkvMF22h2YvgDFrDwqKRUB47NfHzg +3yBpxNH1ccA5/w97QO8w3gX3h6qicpJVOAPusu6cIBACFZfjRv1hyszOZwvw+Soa +Fj5kHkqTY1YpkREPYS9V2dIW1Wjic1SXgZDw7VM/AoGAP/cZ3ZHTSCDTFlItqy5C +rIy2AiY0WJsx+K0qcvtosPOOwtnGjWHb1gdaVdfX/IRkSsX4PAOdnsyidNC5/l/m +y8oa+5WEeGFclWFhr4dnTA766o8HrM2UjIgWWYBF2VKdptGnHxFeJWFUmeQC/xeW +w37pCS7ykL+7gp7V0WShYsw= +-----END PRIVATE KEY----- diff --git a/examples/tls-graceful-shutdown/src/main.rs b/examples/tls-graceful-shutdown/src/main.rs new file mode 100644 index 0000000000..301c65181d --- /dev/null +++ b/examples/tls-graceful-shutdown/src/main.rs @@ -0,0 +1,139 @@ +//! Run with +//! +//! ```not_rust +//! cargo run -p example-tls-graceful-shutdown +//! ``` + +use axum::{ + extract::Host, + handler::HandlerWithoutStateExt, + http::{StatusCode, Uri}, + response::Redirect, + routing::get, + BoxError, Router, +}; +use axum_server::tls_rustls::RustlsConfig; +use std::{future::Future, net::SocketAddr, path::PathBuf, time::Duration}; +use tokio::signal; +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; + +#[derive(Clone, Copy)] +struct Ports { + http: u16, + https: u16, +} + +#[tokio::main] +async fn main() { + tracing_subscriber::registry() + .with( + tracing_subscriber::EnvFilter::try_from_default_env() + .unwrap_or_else(|_| "example_tls_graceful_shutdown=debug".into()), + ) + .with(tracing_subscriber::fmt::layer()) + .init(); + + let ports = Ports { + http: 7878, + https: 3000, + }; + + //Create a handle for our TLS server so the shutdown signal can all shutdown + let handle = axum_server::Handle::new(); + //save the future for easy shutting down of redirect server + let shutdown_future = shutdown_signal(handle.clone()); + + // optional: spawn a second server to redirect http requests to this server + tokio::spawn(redirect_http_to_https(ports, shutdown_future)); + + // configure certificate and private key used by https + let config = RustlsConfig::from_pem_file( + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("self_signed_certs") + .join("cert.pem"), + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("self_signed_certs") + .join("key.pem"), + ) + .await + .unwrap(); + + let app = Router::new().route("/", get(handler)); + + // run https server + let addr = SocketAddr::from(([127, 0, 0, 1], ports.https)); + tracing::debug!("listening on {}", addr); + axum_server::bind_rustls(addr, config) + .handle(handle) + .serve(app.into_make_service()) + .await + .unwrap(); +} + +async fn shutdown_signal(handle: axum_server::Handle) { + let ctrl_c = async { + signal::ctrl_c() + .await + .expect("failed to install Ctrl+C handler"); + }; + + #[cfg(unix)] + let terminate = async { + signal::unix::signal(signal::unix::SignalKind::terminate()) + .expect("failed to install signal handler") + .recv() + .await; + }; + + #[cfg(not(unix))] + let terminate = std::future::pending::<()>(); + + tokio::select! { + _ = ctrl_c => {}, + _ = terminate => {}, + } + + tracing::info!("Received termination signal shutting down"); + handle.graceful_shutdown(Some(Duration::from_secs(10))); // 10 secs is how long docker will wait + // to force shutdown +} + +async fn handler() -> &'static str { + "Hello, World!" +} + +async fn redirect_http_to_https(ports: Ports, signal: impl Future) { + fn make_https(host: String, uri: Uri, ports: Ports) -> Result { + let mut parts = uri.into_parts(); + + parts.scheme = Some(axum::http::uri::Scheme::HTTPS); + + if parts.path_and_query.is_none() { + parts.path_and_query = Some("/".parse().unwrap()); + } + + let https_host = host.replace(&ports.http.to_string(), &ports.https.to_string()); + parts.authority = Some(https_host.parse()?); + + Ok(Uri::from_parts(parts)?) + } + + let redirect = move |Host(host): Host, uri: Uri| async move { + match make_https(host, uri, ports) { + Ok(uri) => Ok(Redirect::permanent(&uri.to_string())), + Err(error) => { + tracing::warn!(%error, "failed to convert URI to HTTPS"); + Err(StatusCode::BAD_REQUEST) + } + } + }; + + let addr = SocketAddr::from(([127, 0, 0, 1], ports.http)); + //let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); + tracing::debug!("listening on {}", &addr); + hyper::Server::bind(&addr) + .serve(redirect.into_make_service()) + .with_graceful_shutdown(signal) + .await + .unwrap(); +}