pax_global_header00006660000000000000000000000064147650243320014520gustar00rootroot0000000000000052 comment=f60b6150bba0aecf4910f877fd0bc12ac24d030b programatik29-axum-server-f60b615/000077500000000000000000000000001476502433200170225ustar00rootroot00000000000000programatik29-axum-server-f60b615/.github/000077500000000000000000000000001476502433200203625ustar00rootroot00000000000000programatik29-axum-server-f60b615/.github/dependabot.yaml000066400000000000000000000005051476502433200233530ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: cargo directory: / schedule: interval: daily - package-ecosystem: github-actions directory: / schedule: interval: daily groups: github-actions: patterns: - "*" ignore: - dependency-name: 'dtolnay/rust-toolchain' programatik29-axum-server-f60b615/.github/workflows/000077500000000000000000000000001476502433200224175ustar00rootroot00000000000000programatik29-axum-server-f60b615/.github/workflows/ci.yml000066400000000000000000000136711476502433200235450ustar00rootroot00000000000000name: CI on: workflow_dispatch: pull_request: push: branches: [master] tags: ["*"] concurrency: group: ${{ github.workflow }}-${{ github.ref_name }} cancel-in-progress: true env: CARGO_TERM_COLOR: always jobs: audit: name: Audit runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Install `cargo-audit` uses: taiki-e/install-action@v2 with: tool: cargo-audit - name: Run Audit run: cargo audit -D warnings rustfmt: name: Rustfmt runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Install Rust uses: dtolnay/rust-toolchain@stable with: components: rustfmt - name: Rust Rustfmt run: cargo fmt --check clippy: name: Clippy ${{ matrix.features.name }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: features: - { features: "" } - { name: "(`tls-rustls`)", features: --features tls-rustls } - { name: "(`tls-rustls-no-provider`)", features: --features tls-rustls-no-provider } - { name: "(`tls-openssl`)", features: --features tls-openssl, openssl: true } - { name: "(`--all-features`)", features: --all-features, openssl: true } steps: - name: Checkout uses: actions/checkout@v4 - name: Install OpenSSL if: matrix.features.openssl == true run: sudo apt-get install pkg-config libssl-dev - name: Install Rust uses: dtolnay/rust-toolchain@stable - name: Run Clippy run: cargo clippy --all-targets ${{ matrix.features.features }} -- -D warnings rustdoc: name: Rustdoc ${{ matrix.rust.name }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: rust: - { version: stable } - { name: "with `cfg(docsrs)`", version: nightly, flags: --cfg=docsrs } steps: - name: Checkout uses: actions/checkout@v4 - name: Install OpenSSL run: sudo apt-get install pkg-config libssl-dev - name: Install Rust uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust.version }} - name: Run Rustdoc env: RUSTDOCFLAGS: -D warnings ${{ matrix.rust.flags }} run: cargo doc --no-deps --document-private-items --lib --examples --all-features build: name: Build ${{ matrix.rust.name }} ${{ matrix.features.name }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: rust: - { version: stable, msrv: false } - { name: "MSRV", version: 1.66, msrv: true } features: - { features: "" } - { name: "(`tls-rustls`)", features: --features tls-rustls } - { name: "(`tls-rustls-no-provider`)", features: --features tls-rustls-no-provider } - { name: "(`tls-openssl`)", features: --features tls-openssl, openssl: true } - { name: "(`--all-features`)", features: --all-features, openssl: true } steps: - name: Checkout uses: actions/checkout@v4 - name: Install OpenSSL if: matrix.features.openssl == true run: sudo apt-get install pkg-config libssl-dev - name: Install Rust uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust.version }} - name: Fix MSRV dependencies if: matrix.rust.msrv == true run: | cargo update -p tokio --precise 1.38.1 cargo update -p tokio-util --precise 0.7.11 - name: Run Cargo Build run: cargo build ${{ matrix.features.features }} test: name: Test ${{ matrix.features.name }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: features: - { features: "" } - { name: "(`tls-rustls`)", features: --features tls-rustls } - { name: "(`rustls/ring`)", features: "--features tls-rustls-no-provider,rustls/ring" } - { name: "(`tls-openssl`)", features: --features tls-openssl, openssl: true } - { name: "(`--all-features`)", features: --all-features, openssl: true } steps: - name: Checkout uses: actions/checkout@v4 - name: Install OpenSSL if: matrix.features.openssl == true run: sudo apt-get install pkg-config libssl-dev - name: Install Rust uses: dtolnay/rust-toolchain@stable - name: Run Cargo Tests run: cargo test --all-targets ${{ matrix.features.features }} --no-fail-fast - name: Rust Documentation Tests run: cargo test --doc ${{ matrix.features.features }} --no-fail-fast minimal-versions: name: Minimal Versions ${{ matrix.features.name }} runs-on: ubuntu-latest defaults: run: working-directory: minimal-versions strategy: fail-fast: false matrix: features: - { features: "" } - { name: "(`tls-rustls`)", features: --features tls-rustls } - { name: "(`tls-rustls-no-provider`)", features: --features tls-rustls-no-provider } - { name: "(`tls-openssl`)", features: --features tls-openssl, openssl: true } - { name: "(`--all-features`)", features: --all-features, openssl: true } steps: - name: Checkout uses: actions/checkout@v4 - name: Install OpenSSL if: matrix.features.openssl == true run: | sudo apt-add-repository "deb http://archive.ubuntu.com/ubuntu focal-updates main" sudo apt update sudo apt-get install --allow-downgrades pkg-config libssl-dev=1.1.1f-1ubuntu2.23 - name: Install Nightly Rust uses: dtolnay/rust-toolchain@nightly - name: Update to Minimal Versions run: cargo update -Zminimal-versions - name: Install Rust uses: dtolnay/rust-toolchain@1.66 - name: Run Cargo Build run: cargo build ${{ matrix.features.features }} programatik29-axum-server-f60b615/.gitignore000066400000000000000000000000231476502433200210050ustar00rootroot00000000000000/target Cargo.lock programatik29-axum-server-f60b615/CHANGELOG.md000066400000000000000000000153201476502433200206340ustar00rootroot00000000000000# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog], and this project adheres to [Semantic Versioning]. # Unreleased None. # 0.7.2 (14. March 2025) - **changed**: Use fs-err to augment errors loading pem files. - **changed**: Updated `tower` from `0.4` to `0.5`. - **added**: Support reading PKCS\#1 and SEC1 private keys with Rustls. # 0.7.1 (31. July 2024) - **added**: Crate feature `tls-rustls-no-provider`, which enables no `rustls::crypto::CryptoProvider`. - **fixed**: Correct minimum required `hyper-util` to `0.1.2`. # 0.7.0 (16. July 2024) - **fixed**: Graceful shutdown now stops accepting requests from existing connections. - **changed**: Updated `rustls` from `0.21` to `0.23`. - **changed**: Updated `tokio-rustls` from `0.24` to `0.26`. - **changed**: Updated `hyper` from `1.0.1` to `1.4`. - **changed**: Updated `http` from `1.0.0` to `1.1`. - **added**: `rustls-pki-types` dependency for the `tls-rustls` feature. - **changed**: Replaced usage of `rustls::Certificate` and `rustls::PrivateKey` with `rustls_pki_types::CertificateDer` and `rustls_pki_types::PrivateKeyDer`. - **changed**: Updated `ServerConfig` initialization to remove `with_safe_defaults()` call. - **changed**: Updated `ClientConfig` initialization in tests to use `dangerous()` instead of `with_safe_defaults()`. - **changed**: Updated `ServerCertVerifier` implementation in tests to match new rustls API. - **changed**: Minor version bumps for various dependencies including `rustls-pemfile`, `serial_test`, and `tower-http`. # 0.6.0 (21. December 2023) - **added**: functionalities in `tls_openssl`, that were added as they appeared to be only in `tls_rustls`: - `axum_server::tls_openssl::OpenSSLConfig::from_acceptor` - `axum_server::tls_openssl::OpenSSLConfig::from_der` - `axum_server::tls_openssl::OpenSSLConfig::from_pem` - `axum_server::tls_openssl::OpenSSLConfig::get_inner` - `axum_server::tls_openssl::OpenSSLConfig::reload_from_der` - `axum_server::tls_openssl::OpenSSLConfig::reload_from_pem` - `axum_server::tls_openssl::OpenSSLConfig::reload_from_pem_file` - `axum_server::tls_openssl::OpenSSLConfig::reload_from_pem_chain_file` - **added**: `from_pem_chain_file` method for `RustlsConfig`. - **breaking**: Removed `HttpConfig` and `AddrIncomingConfig`. - **breaking**: Updated `axum` from `0.6` to `0.7`. - **breaking**: Updated `hyper` to `1.0.1`. # 0.5.1 (15. May 2023) - **added:** `http2_enable_connect_protocol`, `http2_max_header_list_size`, `http2_max_pending_accept_reset_streams` and `http2_max_send_buf_size` methods to `HttpConfig`. # 0.5.0 (4. May 2023) - **breaking:** Updated `rustls` from `0.20` to `0.21` which affects `ServerConfig` type. - **breaking:** Updated `tokio-rustls` from `0.23` to `0.24` which affects `TlsStream` type. # 0.4.7 (19. March 2023) - **added:** Openssl is now supported. # 0.4.5 (18. February 2023) - **changed:** `Handle::graceful_shutdown` now informs connections about the graceful shutdown. # 0.4.3 (3. November 2022) - **added:** Added `tcp_keepalive_interval` and `tcp_keepalive_retries` to `AddrIncomingConfig`. # 0.4.2 (5. August 2022) - **added:** Added `Server::from_tcp`, `axum_server::from_tcp` and `axum_server::from_tcp_rustls` methods to create `Server` from `std::net::TcpListener`. # 0.4.1 (29. July 2022) - **added:** Added `map`, `get` and `get_mut` methods to access the acceptor of `Server`. # 0.4.0 (18. April 2022) - Added TLS handshake timeout(10 seconds). - In `RustlsConfig`: `from_pem` and `from_pem_file` methods now accept EC keys. - **added:** Added `AddrIncomingConfig` to allow configuration of `hyper::server::conn::AddrIncoming`. - **added:** Added `HttpConfig::http1_header_read_timeout`. - **breaking:** Changed `Handle::listening` return type to `Option`. If binding fails, `Option::None` will be returned. # 0.3.2 (17. November 2021) - **added:** Added `HttpConfig` to allow more configuration. # 0.3.1 (10. November 2021) - **fixed:** `tls-rustls` feature doesn't compile if `fs` feature in `tokio` is not enabled. # 0.3.0 (10. November 2021) - **Total rewrite of source code.** - **Major api changes:** - **breaking:** Removed `bind_rustls`, `certificate`, `certificate_file`, `loader`, `new`, `private_key`, `private_key_file`, `serve_and_record`, `tls_config` methods from `Server`. - **breaking:** Removed `tls` module. - **breaking:** Removed `record` module and feature. - **breaking:** Removed `Handle::listening_addrs` method. - **breaking:** `Server::bind` method doesn't take `self` anymore and creates an `Server`. - **breaking:** `bind` method now takes a `SocketAddr`. - **breaking:** `bind_rustls` method now takes a `SocketAddr` and an `tls_rustls::RustlsConfig`. - **breaking:** `Server::serve` method now takes a `MakeService`. - **breaking:** `Handle::listening` method now returns `SocketAddr`. - **added:** Added `Handle::connection_count` that can be used to get alive connection count. - **added:** Added `service` module. - **added:** Added `service::MakeServiceRef` and `service::SendService` traits aliases for convenience. - **added:** Added `accept` module. - **added:** Added `accept::Accept` trait that can be implemented to modify io stream and service. - **added:** Added `accept::DefaultAcceptor` struct that implements `accept::Accept` to be used as a default 'Accept' for 'Server'. - **added:** Added `Server::acceptor` method that can be used to provide a custom `accept::Accept`. - **added:** Added `tls_rustls` module. - **added:** Added `tls_rustls::RustlsAcceptor` that can be used with `Server::acceptor` to make a tls `Server`. - **added:** Added `tls_rustls::RustlsConfig` to create rustls utilities and to provide reload functionality. - **added:** Added `tls_rustls::bind_rustls` which is same as `bind_rustls` function. # 0.2.5 (5. October 2021) - Compile on rust `1.51`. # 0.2.4 (17. September 2021) - Reduced `futures-util` features to improve compile times. # 0.2.3 (14. September 2021) - Fixed `bind` and `bind_rustls` not working on some types. # 0.2.2 (6. September 2021) - Added uri `Scheme` in `Request` extensions. - Fixed memory leak that happens as connections are accepted. # 0.2.1 (30. August 2021) - Fixed `serve_and_record` not recording independently for each connection. # 0.2.0 (29. August 2021) - Added `TlsLoader` to reload tls configuration. - Added `Handle` to provide additional utilities for server. # 0.1.2 (24. August 2021) - Fixed an import issue when using `tls-rustls` feature. # 0.1.0 (23. August 2021) - Initial release. [Keep a Changelog]: https://keepachangelog.com/en/1.0.0/ [Semantic Versioning]: https://semver.org/spec/v2.0.0.html programatik29-axum-server-f60b615/Cargo.toml000066400000000000000000000052461476502433200207610ustar00rootroot00000000000000[package] authors = ["Programatik ", "Adi Salimgereev "] categories = ["asynchronous", "network-programming", "web-programming"] description = "High level server designed to be used with axum framework." edition = "2021" homepage = "https://github.com/programatik29/axum-server" keywords = ["http", "https", "web", "server"] license = "MIT" name = "axum-server" readme = "README.md" repository = "https://github.com/programatik29/axum-server" version = "0.7.2" rust-version = "1.66" [features] default = [] tls-rustls = ["tls-rustls-no-provider", "rustls/aws-lc-rs"] tls-rustls-no-provider = ["arc-swap", "rustls", "rustls-pemfile", "tokio/fs", "tokio/time", "tokio-rustls", "rustls-pki-types", "dep:pin-project-lite"] tls-openssl = ["arc-swap", "openssl", "tokio-openssl", "dep:pin-project-lite"] [dependencies] bytes = "1" fs-err = { version = "3", features = ["tokio"] } http = "1.1" http-body = "1.0" hyper = { version = "1.4", features = ["http1", "http2", "server"] } tokio = { version = "1", features = ["macros", "net", "sync"] } tower-service = "0.3" hyper-util = { version = "0.1.2", features = ["server-auto", "service", "tokio"] } # optional dependencies ## rustls arc-swap = { version = "1", optional = true } rustls = { version = "0.23", default-features = false, optional = true } rustls-pki-types = { version = "1.7", optional = true } rustls-pemfile = { version = "2.1", optional = true } tokio-rustls = { version = "0.26", default-features = false, optional = true } ## openssl openssl = { version = "0.10", optional = true } tokio-openssl = { version = "0.6", optional = true } ## rustls or openssl pin-project-lite = { version = "0.2", optional = true } [dev-dependencies] serial_test = "3.1" futures-util = { version = "0.3", default-features = false } http-body-util = "0.1" axum = "0.7" hyper = { version = "1.4", features = ["full"] } tokio = { version = "1", features = ["full"] } tower = { version = "0.5", features = ["util"] } tower-http = { version = "0.5", features = ["add-extension"] } [package.metadata.docs.rs] all-features = true cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"] rustdoc-args = ["--cfg", "docsrs"] [[example]] name = "from_std_listener_rustls" required-features = ["tls-rustls"] doc-scrape-examples = true [[example]] name = "http_and_https" required-features = ["tls-rustls"] doc-scrape-examples = true [[example]] name = "rustls_reload" required-features = ["tls-rustls"] doc-scrape-examples = true [[example]] name = "rustls_server" required-features = ["tls-rustls"] doc-scrape-examples = true [[example]] name = "rustls_session" required-features = ["tls-rustls"] doc-scrape-examples = true programatik29-axum-server-f60b615/LICENSE000066400000000000000000000020501476502433200200240ustar00rootroot00000000000000Copyright 2021 Axum Server Contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. programatik29-axum-server-f60b615/README.md000066400000000000000000000030751476502433200203060ustar00rootroot00000000000000[![License](https://img.shields.io/crates/l/axum-server)](https://choosealicense.com/licenses/mit/) [![Crates.io](https://img.shields.io/crates/v/axum-server)](https://crates.io/crates/axum-server) [![Docs](https://img.shields.io/crates/v/axum-server?color=blue&label=docs)](https://docs.rs/axum-server/) # axum-server axum-server is a [hyper] server implementation designed to be used with [axum] framework. This project is maintained by community independently from [axum]. ## Features - HTTP/1 and HTTP/2 - HTTPS through [rustls]. - High performance through [hyper]. - Using [tower] make service API. - Very good [axum] compatibility. Likely to work with future [axum] releases. ## Usage Example A simple hello world application can be served like: ```rust use axum::{routing::get, Router}; use std::net::SocketAddr; #[tokio::main] async fn main() { let app = Router::new().route("/", get(|| async { "Hello, world!" })); let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); println!("listening on {}", addr); axum_server::bind(addr) .serve(app.into_make_service()) .await .unwrap(); } ``` You can find more examples [here](/examples). ## Minimum Supported Rust Version axum-server's MSRV is `1.66`. ## Safety This crate uses `#![forbid(unsafe_code)]` to ensure everything is implemented in 100% safe Rust. ## License This project is licensed under the [MIT license](LICENSE). [axum]: https://crates.io/crates/axum [hyper]: https://crates.io/crates/hyper [rustls]: https://crates.io/crates/rustls [tower]: https://crates.io/crates/tower programatik29-axum-server-f60b615/examples/000077500000000000000000000000001476502433200206405ustar00rootroot00000000000000programatik29-axum-server-f60b615/examples/from_std_listener.rs000066400000000000000000000011121476502433200247230ustar00rootroot00000000000000//! Run with `cargo run --example from_std_listener` command. //! //! To connect through browser, navigate to "http://localhost:3000" url. use axum::{routing::get, Router}; use std::net::{SocketAddr, TcpListener}; #[tokio::main] async fn main() { let app = Router::new().route("/", get(|| async { "Hello, world!" })); let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); let listener = TcpListener::bind(addr).unwrap(); println!("listening on {}", addr); axum_server::from_tcp(listener) .serve(app.into_make_service()) .await .unwrap(); } programatik29-axum-server-f60b615/examples/from_std_listener_rustls.rs000066400000000000000000000015131476502433200263440ustar00rootroot00000000000000//! Run with `cargo run --all-features --example from_std_listener_rustls` //! command. //! //! To connect through browser, navigate to "https://localhost:3000" url. use axum::{routing::get, Router}; use axum_server::tls_rustls::RustlsConfig; use std::net::{SocketAddr, TcpListener}; #[tokio::main] async fn main() { let app = Router::new().route("/", get(|| async { "Hello, world!" })); let config = RustlsConfig::from_pem_file( "examples/self-signed-certs/cert.pem", "examples/self-signed-certs/key.pem", ) .await .unwrap(); let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); let listener = TcpListener::bind(addr).unwrap(); println!("listening on {}", addr); axum_server::from_tcp_rustls(listener, config) .serve(app.into_make_service()) .await .unwrap(); } programatik29-axum-server-f60b615/examples/graceful_shutdown.rs000066400000000000000000000027361476502433200247410ustar00rootroot00000000000000//! Run with `cargo run --example graceful_shutdown` command. //! //! To connect through browser, navigate to "http://localhost:3000" url. //! //! After 10 seconds: //! - If there aren't any connections alive, server will shutdown. //! - If there are connections alive, server will wait until deadline is elapsed. //! - Deadline is 30 seconds. Server will shutdown anyways when deadline is elapsed. use axum::{routing::get, Router}; use axum_server::Handle; use std::{net::SocketAddr, time::Duration}; use tokio::time::sleep; #[tokio::main] async fn main() { let app = Router::new().route("/", get(|| async { "Hello, world!" })); let handle = Handle::new(); // Spawn a task to gracefully shutdown server. tokio::spawn(graceful_shutdown(handle.clone())); let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); println!("listening on {}", addr); axum_server::bind(addr) .handle(handle) .serve(app.into_make_service()) .await .unwrap(); println!("server is shut down"); } async fn graceful_shutdown(handle: Handle) { // Wait 10 seconds. sleep(Duration::from_secs(10)).await; println!("sending graceful shutdown signal"); // Signal the server to shutdown using Handle. handle.graceful_shutdown(Some(Duration::from_secs(30))); // Print alive connection count every second. loop { sleep(Duration::from_secs(1)).await; println!("alive connections: {}", handle.connection_count()); } } programatik29-axum-server-f60b615/examples/hello_world.rs000066400000000000000000000007701476502433200235240ustar00rootroot00000000000000//! Run with `cargo run --example hello_world` command. //! //! To connect through browser, navigate to "http://localhost:3000" url. use axum::{routing::get, Router}; use std::net::SocketAddr; #[tokio::main] async fn main() { let app = Router::new().route("/", get(|| async { "Hello, world!" })); let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); println!("listening on {}", addr); axum_server::bind(addr) .serve(app.into_make_service()) .await .unwrap(); } programatik29-axum-server-f60b615/examples/http_and_https.rs000066400000000000000000000026671476502433200242440ustar00rootroot00000000000000//! Run with `cargo run --all-features --example http_and_https` command. //! //! To connect through browser, navigate to "http://localhost:3000" url which should redirect to //! "https://localhost:3443". use axum::{http::uri::Uri, response::Redirect, routing::get, Router}; use axum_server::tls_rustls::RustlsConfig; use std::net::SocketAddr; #[tokio::main] async fn main() { let http = tokio::spawn(http_server()); let https = tokio::spawn(https_server()); // Ignore errors. let _ = tokio::join!(http, https); } async fn http_server() { let app = Router::new().route("/", get(http_handler)); let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); println!("http listening on {}", addr); axum_server::bind(addr) .serve(app.into_make_service()) .await .unwrap(); } async fn http_handler(uri: Uri) -> Redirect { let uri = format!("https://127.0.0.1:3443{}", uri.path()); Redirect::temporary(&uri) } async fn https_server() { let app = Router::new().route("/", get(|| async { "Hello, world!" })); let config = RustlsConfig::from_pem_file( "examples/self-signed-certs/cert.pem", "examples/self-signed-certs/key.pem", ) .await .unwrap(); let addr = SocketAddr::from(([127, 0, 0, 1], 3443)); println!("https listening on {}", addr); axum_server::bind_rustls(addr, config) .serve(app.into_make_service()) .await .unwrap(); } programatik29-axum-server-f60b615/examples/multiple_addresses.rs000066400000000000000000000013061476502433200250760ustar00rootroot00000000000000use axum::{routing::get, Router}; use futures_util::future::try_join_all; use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr}; #[tokio::main] async fn main() { let servers = vec![ SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 3000), SocketAddr::new(Ipv6Addr::LOCALHOST.into(), 3000), ] .into_iter() .map(|addr| tokio::spawn(start_server(addr))); // Returns the first error if any of the servers return an error. try_join_all(servers).await.unwrap(); } async fn start_server(addr: SocketAddr) { let app = Router::new().route("/", get(|| async { "Hello, world!" })); axum_server::bind(addr) .serve(app.into_make_service()) .await .unwrap(); } programatik29-axum-server-f60b615/examples/remote_address.rs000066400000000000000000000011451476502433200242070ustar00rootroot00000000000000//! Run with `cargo run --example remote_address` command. //! //! To connect through browser, navigate to "http://localhost:3000" url. use axum::{extract::ConnectInfo, routing::get, Router}; use std::net::SocketAddr; #[tokio::main] async fn main() { let app = Router::new() .route("/", get(handler)) .into_make_service_with_connect_info::(); let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); axum_server::bind(addr).serve(app).await.unwrap(); } async fn handler(ConnectInfo(addr): ConnectInfo) -> String { format!("your ip address is: {}", addr) } programatik29-axum-server-f60b615/examples/remote_address_using_tower.rs000066400000000000000000000015471476502433200266420ustar00rootroot00000000000000//! Run with `cargo run --example remote_address_using_tower` command. //! //! To connect through browser, navigate to "http://localhost:3000" url. use axum::body::Body; use hyper::{body::Incoming, Request, Response}; use std::{convert::Infallible, net::SocketAddr}; use tower::service_fn; use tower_http::add_extension::AddExtension; #[tokio::main] async fn main() { let service = service_fn(|mut req: Request| async move { let addr: SocketAddr = req.extensions_mut().remove().unwrap(); let body = Body::from(format!("IP Address: {}", addr)); Ok::<_, Infallible>(Response::new(body)) }); axum_server::bind(SocketAddr::from(([127, 0, 0, 1], 3000))) .serve(service_fn(|addr: SocketAddr| async move { Ok::<_, Infallible>(AddExtension::new(service, addr)) })) .await .unwrap(); } programatik29-axum-server-f60b615/examples/rustls_reload.rs000066400000000000000000000026621476502433200240760ustar00rootroot00000000000000//! Run with `cargo run --all-features --example rustls_reload` command. //! //! To connect through browser, navigate to "https://localhost:3000" url. //! //! Certificate common name will be "localhost". //! //! After 20 seconds, certificate common name will be "reloaded". use axum::{routing::get, Router}; use axum_server::tls_rustls::RustlsConfig; use std::{net::SocketAddr, time::Duration}; use tokio::time::sleep; #[tokio::main] async fn main() { let app = Router::new().route("/", get(|| async { "Hello, world!" })); let config = RustlsConfig::from_pem_file( "examples/self-signed-certs/cert.pem", "examples/self-signed-certs/key.pem", ) .await .unwrap(); // Spawn a task to reload tls. tokio::spawn(reload(config.clone())); let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); println!("listening on {}", addr); axum_server::bind_rustls(addr, config) .serve(app.into_make_service()) .await .unwrap(); } async fn reload(config: RustlsConfig) { // Wait for 20 seconds. sleep(Duration::from_secs(20)).await; println!("reloading rustls configuration"); // Reload rustls configuration from new files. config .reload_from_pem_file( "examples/self-signed-certs/reload/cert.pem", "examples/self-signed-certs/reload/key.pem", ) .await .unwrap(); println!("rustls configuration reloaded"); } programatik29-axum-server-f60b615/examples/rustls_server.rs000066400000000000000000000013741476502433200241350ustar00rootroot00000000000000//! Run with `cargo run --all-features --example rustls_server` command. //! //! To connect through browser, navigate to "https://localhost:3000" url. use axum::{routing::get, Router}; use axum_server::tls_rustls::RustlsConfig; use std::net::SocketAddr; #[tokio::main] async fn main() { let app = Router::new().route("/", get(|| async { "Hello, world!" })); let config = RustlsConfig::from_pem_file( "examples/self-signed-certs/cert.pem", "examples/self-signed-certs/key.pem", ) .await .unwrap(); let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); println!("listening on {}", addr); axum_server::tls_rustls::bind_rustls(addr, config) .serve(app.into_make_service()) .await .unwrap(); } programatik29-axum-server-f60b615/examples/rustls_session.rs000066400000000000000000000042071476502433200243100ustar00rootroot00000000000000//! Run with `cargo run --all-features --example rustls_session` command. //! //! To connect through browser, navigate to "https://localhost:3000" url. use axum::{middleware::AddExtension, routing::get, Extension, Router}; use axum_server::{ accept::Accept, tls_rustls::{RustlsAcceptor, RustlsConfig}, }; use futures_util::future::BoxFuture; use std::{io, net::SocketAddr, sync::Arc}; use tokio::io::{AsyncRead, AsyncWrite}; use tokio_rustls::server::TlsStream; use tower::Layer; #[tokio::main] async fn main() { let app = Router::new().route("/", get(handler)); let config = RustlsConfig::from_pem_file( "examples/self-signed-certs/cert.pem", "examples/self-signed-certs/key.pem", ) .await .unwrap(); let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); println!("listening on {}", addr); let acceptor = CustomAcceptor::new(RustlsAcceptor::new(config)); let server = axum_server::bind(addr).acceptor(acceptor); server.serve(app.into_make_service()).await.unwrap(); } async fn handler(tls_data: Extension) -> String { format!("{:?}", tls_data) } #[derive(Debug, Clone)] struct TlsData { _hostname: Option>, } #[derive(Debug, Clone)] struct CustomAcceptor { inner: RustlsAcceptor, } impl CustomAcceptor { fn new(inner: RustlsAcceptor) -> Self { Self { inner } } } impl Accept for CustomAcceptor where I: AsyncRead + AsyncWrite + Unpin + Send + 'static, S: Send + 'static, { type Stream = TlsStream; type Service = AddExtension; type Future = BoxFuture<'static, io::Result<(Self::Stream, Self::Service)>>; fn accept(&self, stream: I, service: S) -> Self::Future { let acceptor = self.inner.clone(); Box::pin(async move { let (stream, service) = acceptor.accept(stream, service).await?; let server_conn = stream.get_ref().1; let sni_hostname = TlsData { _hostname: server_conn.server_name().map(From::from), }; let service = Extension(sni_hostname).layer(service); Ok((stream, service)) }) } } programatik29-axum-server-f60b615/examples/self-signed-certs/000077500000000000000000000000001476502433200241565ustar00rootroot00000000000000programatik29-axum-server-f60b615/examples/self-signed-certs/cert.pem000066400000000000000000000037101476502433200256170ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIFkzCCA3ugAwIBAgIUQZiKeBISKUZoglT8J8CCPpGbgTkwDQYJKoZIhvcNAQEL BQAwWTELMAkGA1UEBhMCVVMxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0MB4X DTIxMDgyOTEyMDE0NVoXDTIyMDgyOTEyMDE0NVowWTELMAkGA1UEBhMCVVMxEzAR BgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5 IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A MIICCgKCAgEAoeDJnuh1lhcpKCt5VEBqO9JcSoz2wqD3SLj4i2qrEOvqb4X0ZZeN 5GQXQlOG2N6+9FOxTzaTTigTecYzI3hqKn1fiuvaS4EeTC7E1sVOj7tY0yVySjXM pC/3t1n1s3B25m7eQ0G2JypZFCobGqY0kaRoO+mCTjI4bdCd769shIerCO4Z8FD5 uj1+hBC7ZY/sqmRkGTLX1ZzkXzaeNeWGlkXKU8/V3qdveFQ/sGe+KoZpOPXb0yR7 H8zf6NE2CFCNJDhytOkYLOsnvCJOvibJ3kbM2GfI9iCd0/QhQAOcrVhcOgI4aIxr wP3zvF4PFUhFKEWHqK5IFq41xKyMYu2fw3bmKXg4zsQGcB0avBD7z+7ENEBvLkNI 7O20wKJp8u0RfjStNHWPmWLXPjkadVB5JHJjsktvgNZkbs9ugxhZWW2AzrrIuqwR NOWnjHE7J3jvcHP6jE5O9LHpnlh6BMoKPsQuRu/bkrD34rNzwH7IX1To1CyDazMR yhUiARYh43gg6hrrQdVjDFMHd51mgWHtOPzSLb0uzToglAa3FClGlCeaiacu4H2V EfJrlCbVlftmIub9/EILZ6XpyYWMxt2mm4mCcMtXmBsHolP4lU3keK8AGNFOr3PC B7NHLNp1RHgx8+Q3kzobJ1Lk+zEjraWPb5gyByUvZySbd/JTGgNCmZsCAwEAAaNT MFEwHQYDVR0OBBYEFGsIv6GsbDS+dEWwWlA/3TG5Oi88MB8GA1UdIwQYMBaAFGsI v6GsbDS+dEWwWlA/3TG5Oi88MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL BQADggIBAHhjzP8WtkLJVfZXXUPAAekR7kaqk2hb3hIgDABBJ7xNxcktLOH7V/ng nhbnwSH5mCkHHXx78TOhWqokHp5wru8K3de5wvAD8uz0UwNDHK5EzqtjYLzxbxAr ht89WoXGPEZIz6MuOxVYx/HHXdgNEXUcujzfpAfvznVxvzBVqpHNgc7qO8wJd0cG nit1XubxKoIVTEUjDfxGa2TsmBI7CZ8MLjIyztp/b3txpVl36hPC/uFLwKC780Jc eO9saA5ISbJh7EaISRr8MKpBpJcraL+055bMjM+kzRFA18NWuuo9Y8fXnXE8e/af k8FvclVdH/YyezaLkjW7lXjo7QoSXHhAuSzvsGmIsh+HuH+3Fs22AN3aGdmimOmp 7JiNe42mwEpJydwgGlKOysw4ht6MA6yOcQJw73QAYYwusOmNjFZtfCUqJx/JO7mn Sb1/PW58xYSJhDxdGhoh6Rd3xPMW1T4YwpapkAC/htciK3XkwCcG1VKSmCIErkXf vllmdahH/QkNooNAHMZl/ipYMik8pp5eRjVjCvpQTDBOI97U0+bgXydHVowP9ExE dGcm6pP8FU1LyBZdYTdlMRC5Z0L0ltcZn7bqKcyzZB3UcWJv7Uhn3MYbmqGsUVly a/e3kH2t5pEWRTsrNrRD94LzEYKvcNHy6PYkrgpGjh2G2VBZgNzh -----END CERTIFICATE----- programatik29-axum-server-f60b615/examples/self-signed-certs/key.pem000066400000000000000000000063041476502433200254540ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- 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 HUGWsrhnzvWoCOV+9+MdApoim8FI -----END PRIVATE KEY----- programatik29-axum-server-f60b615/examples/self-signed-certs/reload/000077500000000000000000000000001476502433200254245ustar00rootroot00000000000000programatik29-axum-server-f60b615/examples/self-signed-certs/reload/cert.pem000066400000000000000000000037101476502433200270650ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIFkTCCA3mgAwIBAgIUXN/Uw2uyZ6/Uj4LRuuK0/RdRHRswDQYJKoZIhvcNAQEL BQAwWDELMAkGA1UEBhMCVVMxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDERMA8GA1UEAwwIcmVsb2FkZWQwHhcN MjEwODI5MTI1NjU2WhcNMjIwODI5MTI1NjU2WjBYMQswCQYDVQQGEwJVUzETMBEG A1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkg THRkMREwDwYDVQQDDAhyZWxvYWRlZDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC AgoCggIBAL/OR5KG8PqJgZSDza1lBVpZjW3jw1MA9eegePoK/4dYjd0Mdw+DeYOu J/UmXoLHUDi/YWwZSmeY3YW0Wimwo1C5VqQL3GapSyFibvyTFE2fpoK0QtlgTKJ4 G0mzdZ9NjibhvK23UOW5VbzlBujrYAaF2ynUha/cgVZ9uzvdwd6ooi+1i6XfHnkG AQqGi6u/SIB+eHXn0w+tTYXmMp44jqIkjsK2vPNeifWj3MQxvgg7JTR/AKTmFCMm BJIEP62BTFEnHJF+pRd2Hj0GIAiNBq1uA1F+HoUhxyX3OWHYCkRwPMnrSbPQOyxO g4oFaUzAvMd2lHN/GjJS0kLwDy7WF/iXZuFxdEsmEmH62fE7N4P2uEnNw5OcHS82 8Mc2EoMrV8zUBl4ZJ2eFo6w9lAx2bzMZyGXdOHsZWnJ5+1co6gfRfv51TeJGQx8f JaHWFrn55qKBQmgQpKmCt/sG3HrqTviw1PtecsrzTliEXPoWdx6AhYaV+I4u8c8S Q0NfdfjXx+5EMFDe5CvfWp/D5C1AQIV5E0Ao3Q+VfjoU/2tz9WcE5voHfyl3mBMI FHvAPCZC18E+ZpiYyhRJLxP4z0MzTiuxp25lRi0Yt/5QTzEzFfH1UNQYe2xljPtf syg5RtHoijcL+MncE1NUXz+B/qC4uJm8llPjFoL94Yg3/dwWOPtPAgMBAAGjUzBR MB0GA1UdDgQWBBThri9Jq8CLFMEHJ+wE7WsCODVRwjAfBgNVHSMEGDAWgBThri9J q8CLFMEHJ+wE7WsCODVRwjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUA A4ICAQCiR8yJ2YQyJfYDd9BT9eb9H8/S+Yz/9ayNS3zSJk4StQZaS1V6XjexzDBr MRSr/hHGtO9G2qeocuJ/ArUJS5yYsf69g9AjuB+b41k0E4BVpiB/lENAhMbMbl+D +ysRifUR2svHnZzKnL7DRrpS3vEUQhO37GXwbEi192rXAr2N6VE0LhxGyE7EwCzw 7gNkzoB3/Y4Fb+6zCYZorg3PmPZHrfu9vGFiP9nh+JVos9aq2JHZgZJ2N5Hcdh1H Bci372+i1SHKfYutXrcSnUcPd4UgGQt6F63fOFHJEGsSVbHpJujqjpIscuPqgfn8 DSkm9SEyVEV8MrY2vtwtVFOre4yjsaZ2fHDU7rCXOO88kIBBdvIpdIO4mBKV14ug k9M1xzqK/KvgMUztuw/oLxOp7Vnii9sQ9bjzjbFEMiJ07V5Egr88Zh+VnN3ED1MH Ri6Ho/CI/ttAwzZVhrKumOb6AprPVUteZFedpV80UaYmIthkeW0i9QcUOMkr4bL3 gCghJeBSETTGEYCKOpcIFbvXwlc8d3KlL0Fa4EbQiw5vlPY28UChnxuZ3I0Vtetf 2F+3bLoVxfZD2Gc7p5bjGHgzUbGLFM4GgqQ6EbRh261Om9/bUxBao7mhKa23XWna 3Y4qISAqus6OolerflYJCCuWUF4N6e6fES5bqnZD49qAaIEg0A== -----END CERTIFICATE----- programatik29-axum-server-f60b615/examples/self-signed-certs/reload/key.pem000066400000000000000000000063101476502433200267170ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- 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 85DaC33fAFuevGxpS147+sAiW6doqQ== -----END PRIVATE KEY----- programatik29-axum-server-f60b615/examples/shutdown.rs000066400000000000000000000017661476502433200230730ustar00rootroot00000000000000//! Run with `cargo run --example shutdown` command. //! //! To connect through browser, navigate to "http://localhost:3000" url. //! //! Server will shutdown in 20 seconds. use axum::{routing::get, Router}; use axum_server::Handle; use std::{net::SocketAddr, time::Duration}; use tokio::time::sleep; #[tokio::main] async fn main() { let app = Router::new().route("/", get(|| async { "Hello, world!" })); let handle = Handle::new(); // Spawn a task to shutdown server. tokio::spawn(shutdown(handle.clone())); let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); println!("listening on {}", addr); axum_server::bind(addr) .handle(handle) .serve(app.into_make_service()) .await .unwrap(); println!("server is shut down"); } async fn shutdown(handle: Handle) { // Wait 20 seconds. sleep(Duration::from_secs(20)).await; println!("sending shutdown signal"); // Signal the server to shutdown using Handle. handle.shutdown(); } programatik29-axum-server-f60b615/minimal-versions/000077500000000000000000000000001476502433200223165ustar00rootroot00000000000000programatik29-axum-server-f60b615/minimal-versions/.gitignore000066400000000000000000000000241476502433200243020ustar00rootroot00000000000000/target /Cargo.lock programatik29-axum-server-f60b615/minimal-versions/Cargo.toml000066400000000000000000000005711476502433200242510ustar00rootroot00000000000000[package] edition = "2021" name = "minimal-versions" publish = false version = "0.0.0" [features] tls-rustls = ["axum-server/tls-rustls"] tls-rustls-no-provider = ["axum-server/tls-rustls-no-provider"] tls-openssl = ["axum-server/tls-openssl"] [dependencies] axum-server = { path = ".." } # `tower` v0.5.0 incorrectly only requires `tower-layer` v0.3.0 tower-layer = "0.3.3" programatik29-axum-server-f60b615/minimal-versions/src/000077500000000000000000000000001476502433200231055ustar00rootroot00000000000000programatik29-axum-server-f60b615/minimal-versions/src/lib.rs000066400000000000000000000000561476502433200242220ustar00rootroot00000000000000//! Testing minimal versions of dependencies. programatik29-axum-server-f60b615/src/000077500000000000000000000000001476502433200176115ustar00rootroot00000000000000programatik29-axum-server-f60b615/src/accept.rs000066400000000000000000000031311476502433200214140ustar00rootroot00000000000000//! [`Accept`] trait and utilities. use std::{ future::{Future, Ready}, io, }; use tokio::net::TcpStream; /// An asynchronous function to modify io stream and service. pub trait Accept { /// IO stream produced by accept. type Stream; /// Service produced by accept. type Service; /// Future return value. type Future: Future>; /// Process io stream and service asynchronously. fn accept(&self, stream: I, service: S) -> Self::Future; } /// A no-op acceptor. #[derive(Clone, Copy, Debug, Default)] pub struct DefaultAcceptor; impl DefaultAcceptor { /// Create a new default acceptor. pub fn new() -> Self { Self } } impl Accept for DefaultAcceptor { type Stream = I; type Service = S; type Future = Ready>; fn accept(&self, stream: I, service: S) -> Self::Future { std::future::ready(Ok((stream, service))) } } /// An acceptor that sets `TCP_NODELAY` on accepted streams. #[derive(Clone, Copy, Debug, Default)] pub struct NoDelayAcceptor; impl NoDelayAcceptor { /// Create a new acceptor that sets `TCP_NODELAY` on accepted streams. pub fn new() -> Self { Self } } impl Accept for NoDelayAcceptor { type Stream = TcpStream; type Service = S; type Future = Ready>; fn accept(&self, stream: TcpStream, service: S) -> Self::Future { std::future::ready(stream.set_nodelay(true).and(Ok((stream, service)))) } } programatik29-axum-server-f60b615/src/handle.rs000066400000000000000000000064151476502433200214200ustar00rootroot00000000000000use crate::notify_once::NotifyOnce; use std::{ net::SocketAddr, sync::{ atomic::{AtomicUsize, Ordering}, Arc, Mutex, }, time::Duration, }; use tokio::{sync::Notify, time::sleep}; /// A handle for [`Server`](crate::server::Server). #[derive(Clone, Debug, Default)] pub struct Handle { inner: Arc, } #[derive(Debug, Default)] struct HandleInner { addr: Mutex>, addr_notify: Notify, conn_count: AtomicUsize, shutdown: NotifyOnce, graceful: NotifyOnce, graceful_dur: Mutex>, conn_end: NotifyOnce, } impl Handle { /// Create a new handle. pub fn new() -> Self { Self::default() } /// Get the number of connections. pub fn connection_count(&self) -> usize { self.inner.conn_count.load(Ordering::SeqCst) } /// Shutdown the server. pub fn shutdown(&self) { self.inner.shutdown.notify_waiters(); } /// Gracefully shutdown the server. /// /// `None` means indefinite grace period. pub fn graceful_shutdown(&self, duration: Option) { *self.inner.graceful_dur.lock().unwrap() = duration; self.inner.graceful.notify_waiters(); } /// Returns local address and port when server starts listening. /// /// Returns `None` if server fails to bind. pub async fn listening(&self) -> Option { let notified = self.inner.addr_notify.notified(); if let Some(addr) = *self.inner.addr.lock().unwrap() { return Some(addr); } notified.await; *self.inner.addr.lock().unwrap() } pub(crate) fn notify_listening(&self, addr: Option) { *self.inner.addr.lock().unwrap() = addr; self.inner.addr_notify.notify_waiters(); } pub(crate) fn watcher(&self) -> Watcher { Watcher::new(self.clone()) } pub(crate) async fn wait_shutdown(&self) { self.inner.shutdown.notified().await; } pub(crate) async fn wait_graceful_shutdown(&self) { self.inner.graceful.notified().await; } pub(crate) async fn wait_connections_end(&self) { if self.inner.conn_count.load(Ordering::SeqCst) == 0 { return; } let deadline = *self.inner.graceful_dur.lock().unwrap(); match deadline { Some(duration) => tokio::select! { biased; _ = sleep(duration) => self.shutdown(), _ = self.inner.conn_end.notified() => (), }, None => self.inner.conn_end.notified().await, } } } pub(crate) struct Watcher { handle: Handle, } impl Watcher { fn new(handle: Handle) -> Self { handle.inner.conn_count.fetch_add(1, Ordering::SeqCst); Self { handle } } pub(crate) async fn wait_graceful_shutdown(&self) { self.handle.wait_graceful_shutdown().await } pub(crate) async fn wait_shutdown(&self) { self.handle.wait_shutdown().await } } impl Drop for Watcher { fn drop(&mut self) { let count = self.handle.inner.conn_count.fetch_sub(1, Ordering::SeqCst) - 1; if count == 0 && self.handle.inner.graceful.is_notified() { self.handle.inner.conn_end.notify_waiters(); } } } programatik29-axum-server-f60b615/src/lib.rs000066400000000000000000000077551476502433200207430ustar00rootroot00000000000000//! axum-server is a [hyper] server implementation designed to be used with [axum] framework. //! //! # Features //! //! - HTTP/1 and HTTP/2 //! - HTTPS through [rustls] or [openssl]. //! - High performance through [hyper]. //! - Using [tower] make service API. //! - Very good [axum] compatibility. Likely to work with future [axum] releases. //! //! # Guide //! //! axum-server can [`serve`] items that implement [`MakeService`] with some additional [trait //! bounds](crate::service::MakeService). Make services that are [created] using [`axum`] //! complies with those trait bounds out of the box. Therefore it is more convenient to use this //! crate with [`axum`]. //! //! All examples in this crate uses [`axum`]. If you want to use this crate without [`axum`] it is //! highly recommended to learn how [tower] works. //! //! [`Server::bind`] or [`bind`] function can be called to create a server that will bind to //! provided [`SocketAddr`] when [`serve`] is called. //! //! A [`Handle`] can be passed to [`Server`](Server::handle) for additional utilities like shutdown //! and graceful shutdown. //! //! [`bind_rustls`] can be called by providing [`RustlsConfig`] to create a HTTPS [`Server`] that //! will bind on provided [`SocketAddr`]. [`RustlsConfig`] can be cloned, reload methods can be //! used on clone to reload tls configuration. //! //! # Features //! //! * `tls-rustls` - activate [rustls] support. //! * `tls-rustls-no-provider` - activate [rustls] support without a default provider. //! * `tls-openssl` - activate [openssl] support. //! //! # Example //! //! A simple hello world application can be served like: //! //! ```rust,no_run //! use axum::{routing::get, Router}; //! use std::net::SocketAddr; //! //! #[tokio::main] //! async fn main() { //! let app = Router::new().route("/", get(|| async { "Hello, world!" })); //! //! let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); //! println!("listening on {}", addr); //! axum_server::bind(addr) //! .serve(app.into_make_service()) //! .await //! .unwrap(); //! } //! ``` //! //! You can find more examples in [repository]. //! //! [axum]: https://crates.io/crates/axum //! [bind]: crate::bind //! [bind_rustls]: crate::bind_rustls //! [created]: https://docs.rs/axum/0.3/axum/struct.Router.html#method.into_make_service //! [hyper]: https://crates.io/crates/hyper //! [openssl]: https://crates.io/crates/openssl //! [repository]: https://github.com/programatik29/axum-server/tree/v0.3.0/examples //! [rustls]: https://crates.io/crates/rustls //! [tower]: https://crates.io/crates/tower //! [`axum`]: https://docs.rs/axum/0.3 //! [`serve`]: crate::server::Server::serve //! [`MakeService`]: https://docs.rs/tower/0.4/tower/make/trait.MakeService.html //! [`RustlsConfig`]: crate::tls_rustls::RustlsConfig //! [`SocketAddr`]: std::net::SocketAddr #![forbid(unsafe_code)] #![warn( clippy::await_holding_lock, clippy::cargo_common_metadata, clippy::dbg_macro, clippy::doc_markdown, clippy::empty_enum, clippy::enum_glob_use, clippy::inefficient_to_string, clippy::mem_forget, clippy::mutex_integer, clippy::needless_continue, clippy::todo, clippy::unimplemented, clippy::wildcard_imports, future_incompatible, missing_docs, missing_debug_implementations, unreachable_pub )] #![cfg_attr(docsrs, feature(doc_cfg))] mod handle; mod notify_once; mod server; pub mod accept; pub mod service; pub use self::{ // addr_incoming_config::AddrIncomingConfig, handle::Handle, // http_config::HttpConfig, server::{bind, from_tcp, Server}, }; #[cfg(feature = "tls-rustls-no-provider")] #[cfg_attr(docsrs, doc(cfg(feature = "tls-rustls")))] pub mod tls_rustls; #[doc(inline)] #[cfg(feature = "tls-rustls-no-provider")] pub use self::tls_rustls::export::{bind_rustls, from_tcp_rustls}; #[cfg(feature = "tls-openssl")] #[cfg_attr(docsrs, doc(cfg(feature = "tls-openssl")))] pub mod tls_openssl; #[doc(inline)] #[cfg(feature = "tls-openssl")] pub use self::tls_openssl::bind_openssl; programatik29-axum-server-f60b615/src/notify_once.rs000066400000000000000000000011541476502433200224740ustar00rootroot00000000000000use std::sync::atomic::{AtomicBool, Ordering}; use tokio::sync::Notify; #[derive(Debug, Default)] pub(crate) struct NotifyOnce { notified: AtomicBool, notify: Notify, } impl NotifyOnce { pub(crate) fn notify_waiters(&self) { self.notified.store(true, Ordering::SeqCst); self.notify.notify_waiters(); } pub(crate) fn is_notified(&self) -> bool { self.notified.load(Ordering::SeqCst) } pub(crate) async fn notified(&self) { let future = self.notify.notified(); if !self.notified.load(Ordering::SeqCst) { future.await; } } } programatik29-axum-server-f60b615/src/server.rs000066400000000000000000000417671476502433200215040ustar00rootroot00000000000000use crate::{ accept::{Accept, DefaultAcceptor}, handle::Handle, service::{MakeService, SendService}, }; use http::Request; use hyper::body::Incoming; use hyper_util::{ rt::{TokioExecutor, TokioIo}, server::conn::auto::Builder, service::TowerToHyperService, }; use std::{ fmt, future::poll_fn, io::{self, ErrorKind}, net::SocketAddr, time::Duration, }; use tokio::{ io::{AsyncRead, AsyncWrite}, net::{TcpListener, TcpStream}, }; /// HTTP server. pub struct Server { acceptor: A, builder: Builder, listener: Listener, handle: Handle, } // Builder doesn't implement Debug or Clone right now impl fmt::Debug for Server where A: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Server") .field("acceptor", &self.acceptor) .field("listener", &self.listener) .field("handle", &self.handle) .finish_non_exhaustive() } } #[derive(Debug)] enum Listener { Bind(SocketAddr), Std(std::net::TcpListener), } /// Create a [`Server`] that will bind to provided address. pub fn bind(addr: SocketAddr) -> Server { Server::bind(addr) } /// Create a [`Server`] from existing `std::net::TcpListener`. pub fn from_tcp(listener: std::net::TcpListener) -> Server { Server::from_tcp(listener) } impl Server { /// Create a server that will bind to provided address. pub fn bind(addr: SocketAddr) -> Self { let acceptor = DefaultAcceptor::new(); let builder = Builder::new(TokioExecutor::new()); let handle = Handle::new(); Self { acceptor, builder, listener: Listener::Bind(addr), handle, } } /// Create a server from existing `std::net::TcpListener`. pub fn from_tcp(listener: std::net::TcpListener) -> Self { let acceptor = DefaultAcceptor::new(); let builder = Builder::new(TokioExecutor::new()); let handle = Handle::new(); Self { acceptor, builder, listener: Listener::Std(listener), handle, } } } impl Server { /// Overwrite acceptor. pub fn acceptor(self, acceptor: Acceptor) -> Server { Server { acceptor, builder: self.builder, listener: self.listener, handle: self.handle, } } /// Map acceptor. pub fn map(self, acceptor: F) -> Server where F: FnOnce(A) -> Acceptor, { Server { acceptor: acceptor(self.acceptor), builder: self.builder, listener: self.listener, handle: self.handle, } } /// Returns a reference to the acceptor. pub fn get_ref(&self) -> &A { &self.acceptor } /// Returns a mutable reference to the acceptor. pub fn get_mut(&mut self) -> &mut A { &mut self.acceptor } /// Returns a mutable reference to the Http builder. pub fn http_builder(&mut self) -> &mut Builder { &mut self.builder } /// Provide a handle for additional utilities. pub fn handle(mut self, handle: Handle) -> Self { self.handle = handle; self } /// Serve provided [`MakeService`]. /// /// To create [`MakeService`] easily, `Shared` from [`tower`] can be used. /// /// # Errors /// /// An error will be returned when: /// /// - Binding to an address fails. /// - `make_service` returns an error when `poll_ready` is called. This never happens on /// [`axum`] make services. /// /// [`axum`]: https://docs.rs/axum/0.3 /// [`tower`]: https://docs.rs/tower /// [`MakeService`]: https://docs.rs/tower/0.4/tower/make/trait.MakeService.html pub async fn serve(self, mut make_service: M) -> io::Result<()> where M: MakeService>, A: Accept + Clone + Send + Sync + 'static, A::Stream: AsyncRead + AsyncWrite + Unpin + Send, A::Service: SendService> + Send, A::Future: Send, { let acceptor = self.acceptor; let handle = self.handle; let builder = std::sync::Arc::new(self.builder); let mut incoming = match bind_incoming(self.listener).await { Ok(v) => v, Err(e) => { handle.notify_listening(None); return Err(e); } }; handle.notify_listening(incoming.local_addr().ok()); let accept_loop_future = async { loop { let (tcp_stream, socket_addr) = tokio::select! { biased; result = accept(&mut incoming) => result, _ = handle.wait_graceful_shutdown() => return Ok(()), }; poll_fn(|cx| make_service.poll_ready(cx)) .await .map_err(io_other)?; let service = match make_service.make_service(socket_addr).await { Ok(service) => service, Err(_) => continue, }; let acceptor = acceptor.clone(); let watcher = handle.watcher(); let builder = builder.clone(); tokio::spawn(async move { if let Ok((stream, send_service)) = acceptor.accept(tcp_stream, service).await { let io = TokioIo::new(stream); let service = send_service.into_service(); let service = TowerToHyperService::new(service); let serve_future = builder.serve_connection_with_upgrades(io, service); tokio::pin!(serve_future); tokio::select! { biased; _ = watcher.wait_graceful_shutdown() => { serve_future.as_mut().graceful_shutdown(); tokio::select! { biased; _ = watcher.wait_shutdown() => (), _ = &mut serve_future => (), } } _ = watcher.wait_shutdown() => (), _ = &mut serve_future => (), } } }); } }; let result = tokio::select! { biased; _ = handle.wait_shutdown() => return Ok(()), result = accept_loop_future => result, }; // Tokio internally accepts TCP connections while the TCPListener is active; // drop the listener to immediately refuse connections rather than letting // them hang. drop(incoming); // attempting to do a "result?;" requires us to specify the type of result which is annoying #[allow(clippy::question_mark)] if let Err(e) = result { return Err(e); } handle.wait_connections_end().await; Ok(()) } } async fn bind_incoming(listener: Listener) -> io::Result { match listener { Listener::Bind(addr) => TcpListener::bind(addr).await, Listener::Std(std_listener) => { std_listener.set_nonblocking(true)?; TcpListener::from_std(std_listener) } } } pub(crate) async fn accept(listener: &mut TcpListener) -> (TcpStream, SocketAddr) { loop { match listener.accept().await { Ok(value) => return value, Err(_) => tokio::time::sleep(Duration::from_millis(50)).await, } } } type BoxError = Box; pub(crate) fn io_other>(error: E) -> io::Error { io::Error::new(ErrorKind::Other, error) } #[cfg(test)] mod tests { use crate::{handle::Handle, server::Server}; use axum::body::Body; use axum::response::Response; use axum::routing::post; use axum::{routing::get, Router}; use bytes::Bytes; use futures_util::{stream, StreamExt}; use http::{Method, Request, Uri}; use http_body::Frame; use http_body_util::{BodyExt, StreamBody}; use hyper::client::conn::http1::handshake; use hyper::client::conn::http1::SendRequest; use hyper_util::rt::TokioIo; use std::{io, net::SocketAddr, time::Duration}; use tokio::sync::oneshot; use tokio::{net::TcpStream, task::JoinHandle, time::timeout}; #[tokio::test] async fn start_and_request() { let (_handle, _server_task, addr) = start_server().await; let (mut client, _conn) = connect(addr).await; // Client can send requests do_empty_request(&mut client).await.unwrap(); do_slow_request(&mut client, Duration::from_millis(50)) .await .unwrap(); } #[tokio::test] async fn test_shutdown() { let (handle, _server_task, addr) = start_server().await; let (mut client, conn) = connect(addr).await; // Client can send request before shutdown. do_empty_request(&mut client).await.unwrap(); handle.shutdown(); // After shutdown, all client requests should fail. do_empty_request(&mut client).await.unwrap_err(); // Connection should finish soon. let _ = timeout(Duration::from_secs(1), conn).await.unwrap(); } // Test graceful shutdown with no timeout. #[tokio::test] async fn test_graceful_shutdown_no_timeout() { let (handle, server_task, addr) = start_server().await; let (mut client1, _conn1) = connect(addr).await; let (mut client2, _conn2) = connect(addr).await; // Clients can send request before graceful shutdown. do_empty_request(&mut client1).await.unwrap(); do_empty_request(&mut client2).await.unwrap(); let start = tokio::time::Instant::now(); let (hdr1_tx, hdr1_rx) = oneshot::channel::<()>(); let fut1 = async { // A slow request made before graceful shutdown is handled. // Since there's no request timeout, this can take as long as it // needs. let hdr1 = send_slow_request(&mut client1, Duration::from_millis(500)) .await .unwrap(); hdr1_tx.send(()).unwrap(); recv_slow_response_body(hdr1).await.unwrap(); assert!(start.elapsed() >= Duration::from_millis(500)); }; let fut2 = async { // Graceful shutdown partway through tokio::time::sleep(Duration::from_millis(250)).await; hdr1_rx.await.unwrap(); handle.graceful_shutdown(None); // Any new requests after graceful shutdown begins will fail do_empty_request(&mut client2).await.unwrap_err(); do_empty_request(&mut client2).await.unwrap_err(); do_empty_request(&mut client2).await.unwrap_err(); }; tokio::join!(fut1, fut2); // At this point, graceful shutdown must have occured, and the slow // request must have finished. Since there was no timeout, the elapsed // time should be at least 500 ms (slow request duration). assert!(start.elapsed() >= Duration::from_millis(500 + 100)); // Server task should finish soon. timeout(Duration::from_secs(1), server_task) .await .unwrap() .unwrap() .unwrap(); } // Test graceful shutdown with a timeout. #[tokio::test] async fn test_graceful_shutdown_timeout() { let (handle, server_task, addr) = start_server().await; let (mut client1, _conn1) = connect(addr).await; let (mut client2, _conn2) = connect(addr).await; // Clients can send request before graceful shutdown. do_empty_request(&mut client1).await.unwrap(); do_empty_request(&mut client2).await.unwrap(); let start = tokio::time::Instant::now(); let (hdr1_tx, hdr1_rx) = oneshot::channel::<()>(); let task1 = async { // A slow request made before graceful shutdown is handled. // This one is shorter than the timeout, so it should succeed. let hdr1 = send_slow_request(&mut client1, Duration::from_millis(222)).await; hdr1_tx.send(()).unwrap(); let res1 = recv_slow_response_body(hdr1.unwrap()).await; res1.unwrap(); }; let task2 = async { // A slow request made before graceful shutdown is handled. // This one is much longer than the timeout; it should fail sometime // after the graceful shutdown timeout. let hdr2 = send_slow_request(&mut client2, Duration::from_millis(5_555)).await; hdr2.unwrap_err(); }; let task3 = async { // Begin graceful shutdown after we receive response headers for (1). hdr1_rx.await.unwrap(); // Set a timeout on requests to finish before we drop them. handle.graceful_shutdown(Some(Duration::from_millis(333))); // Server task should finish soon. timeout(Duration::from_secs(1), server_task) .await .unwrap() .unwrap() .unwrap(); // At this point, graceful shutdown must have occured. assert!(start.elapsed() >= Duration::from_millis(222 + 333)); assert!(start.elapsed() <= Duration::from_millis(5_555)); }; tokio::join!(task1, task2, task3); } async fn start_server() -> (Handle, JoinHandle>, SocketAddr) { let handle = Handle::new(); let server_handle = handle.clone(); let server_task = tokio::spawn(async move { let app = Router::new() .route("/", get(|| async { "Hello, world!" })) .route( "/echo_slowly", post(|body: Bytes| async move { // Stream a response slowly, byte-by-byte, over 100ms Response::new(slow_body(body.len(), Duration::from_millis(100))) }), ); let addr = SocketAddr::from(([127, 0, 0, 1], 0)); Server::bind(addr) .handle(server_handle) .serve(app.into_make_service()) .await }); let addr = handle.listening().await.unwrap(); (handle, server_task, addr) } async fn connect(addr: SocketAddr) -> (SendRequest, JoinHandle<()>) { let stream = TokioIo::new(TcpStream::connect(addr).await.unwrap()); let (send_request, connection) = handshake(stream).await.unwrap(); let task = tokio::spawn(async move { let _ = connection.await; }); (send_request, task) } // Send a basic `GET /` request. async fn do_empty_request(client: &mut SendRequest) -> hyper::Result<()> { client.ready().await?; let body = client .send_request(Request::new(Body::empty())) .await? .into_body(); let body = body.collect().await?.to_bytes(); assert_eq!(body.as_ref(), b"Hello, world!"); Ok(()) } // Send a request with a body streamed byte-by-byte, over a given duration, // then wait for the full response. async fn do_slow_request( client: &mut SendRequest, duration: Duration, ) -> hyper::Result<()> { let response = send_slow_request(client, duration).await?; recv_slow_response_body(response).await } async fn send_slow_request( client: &mut SendRequest, duration: Duration, ) -> hyper::Result> { let req_body_len: usize = 10; let mut req = Request::new(slow_body(req_body_len, duration)); *req.method_mut() = Method::POST; *req.uri_mut() = Uri::from_static("/echo_slowly"); client.ready().await?; client.send_request(req).await } async fn recv_slow_response_body( response: http::Response, ) -> hyper::Result<()> { let resp_body = response.into_body(); let resp_body_bytes = resp_body.collect().await?.to_bytes(); assert_eq!(10, resp_body_bytes.len()); Ok(()) } // A stream of n response data `Frame`s, where n = `length`, and each frame // consists of a single byte. The whole response is smeared out over // a `duration` length of time. fn slow_body(length: usize, duration: Duration) -> axum::body::Body { let frames = (0..length).map(move |_| Ok::<_, hyper::Error>(Frame::data(Bytes::from_static(b"X")))); let stream = stream::iter(frames).then(move |frame| async move { tokio::time::sleep(duration / (length as u32)).await; frame }); axum::body::Body::new(StreamBody::new(stream)) } } programatik29-axum-server-f60b615/src/service.rs000066400000000000000000000113361476502433200216230ustar00rootroot00000000000000//! Service traits. use http::Response; use http_body::Body; use std::{ future::Future, task::{Context, Poll}, }; use tower_service::Service; /// Trait alias for [`Service`] with bounds required for [`serve`](crate::server::Server::serve). /// /// This trait is sealed and cannot be implemented for types outside this crate. #[allow(missing_docs)] pub trait SendService: send_service::Sealed { type Service: Service< Request, Response = Response, Error = Self::Error, Future = Self::Future, > + Send + Clone + 'static; type Body: Body + Send + 'static; type BodyData: Send + 'static; type BodyError: Into>; type Error: Into>; type Future: Future, Self::Error>> + Send + 'static; fn into_service(self) -> Self::Service; } impl send_service::Sealed for T where T: Service>, T::Error: Into>, T::Future: Send + 'static, B: Body + Send + 'static, B::Data: Send + 'static, B::Error: Into>, { } impl SendService for T where T: Service> + Send + Clone + 'static, T::Error: Into>, T::Future: Send + 'static, B: Body + Send + 'static, B::Data: Send + 'static, B::Error: Into>, { type Service = T; type Body = B; type BodyData = B::Data; type BodyError = B::Error; type Error = T::Error; type Future = T::Future; fn into_service(self) -> Self::Service { self } } /// Modified version of [`MakeService`] that takes a `&Target` and has required trait bounds for /// [`serve`](crate::server::Server::serve). /// /// This trait is sealed and cannot be implemented for types outside this crate. /// /// [`MakeService`]: https://docs.rs/tower/0.4/tower/make/trait.MakeService.html #[allow(missing_docs)] pub trait MakeService: make_service_ref::Sealed<(Target, Request)> { type Service: Service< Request, Response = Response, Error = Self::Error, Future = Self::Future, > + Send + 'static + Clone; type Body: Body + Send + 'static; type BodyData: Send + 'static; type BodyError: Into>; type Error: Into>; type Future: Future, Self::Error>> + Send + 'static; type MakeError: Into>; type MakeFuture: Future>; fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll>; fn make_service(&mut self, target: Target) -> Self::MakeFuture; } impl make_service_ref::Sealed<(Target, Request)> for T where T: Service, S: Service> + Send + 'static, S::Error: Into>, S::Future: Send + 'static, B: Body + Send + 'static, B::Data: Send + 'static, B::Error: Into>, E: Into>, F: Future>, { } impl MakeService for T where T: Service, S: Service> + Send + Clone + 'static, S::Error: Into>, S::Future: Send + 'static, B: Body + Send + 'static, B::Data: Send + 'static, B::Error: Into>, E: Into>, F: Future>, { type Service = S; type Body = B; type BodyData = B::Data; type BodyError = B::Error; type Error = S::Error; type Future = S::Future; type MakeError = E; type MakeFuture = F; fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { self.poll_ready(cx) } fn make_service(&mut self, target: Target) -> Self::MakeFuture { self.call(target) } } mod send_service { pub trait Sealed {} } mod make_service_ref { pub trait Sealed {} } programatik29-axum-server-f60b615/src/tls_openssl/000077500000000000000000000000001476502433200221565ustar00rootroot00000000000000programatik29-axum-server-f60b615/src/tls_openssl/future.rs000066400000000000000000000127431476502433200240450ustar00rootroot00000000000000//! Future types. use super::OpenSSLConfig; use pin_project_lite::pin_project; use std::io::{Error, ErrorKind}; use std::time::Duration; use std::{ fmt, future::Future, io, pin::Pin, task::{Context, Poll}, }; use tokio::io::{AsyncRead, AsyncWrite}; use tokio::time::{timeout, Timeout}; use openssl::ssl::Ssl; use tokio_openssl::SslStream; pin_project! { /// Future type for [`OpenSSLAcceptor`](crate::tls_openssl::OpenSSLAcceptor). pub struct OpenSSLAcceptorFuture { #[pin] inner: AcceptFuture, config: Option, } } impl OpenSSLAcceptorFuture { pub(crate) fn new(future: F, config: OpenSSLConfig, handshake_timeout: Duration) -> Self { let inner = AcceptFuture::InnerAccepting { future, handshake_timeout, }; let config = Some(config); Self { inner, config } } } impl fmt::Debug for OpenSSLAcceptorFuture { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("OpenSSLAcceptorFuture").finish() } } pin_project! { struct TlsAccept { #[pin] tls_stream: Option>, } } impl Future for TlsAccept where I: AsyncRead + AsyncWrite + Unpin, { type Output = io::Result>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let mut this = self.project(); match this .tls_stream .as_mut() .as_pin_mut() .map(|inner| inner.poll_accept(cx)) .expect("tlsaccept polled after ready") { Poll::Ready(Ok(())) => { let tls_stream = this.tls_stream.take().expect("tls stream vanished?"); Poll::Ready(Ok(tls_stream)) } Poll::Ready(Err(e)) => Poll::Ready(Err(Error::new(ErrorKind::Other, e))), Poll::Pending => Poll::Pending, } } } pin_project! { #[project = AcceptFutureProj] enum AcceptFuture { // We are waiting on the inner (lower) future to complete accept() // so that we can begin installing TLS into the channel. InnerAccepting { #[pin] future: F, handshake_timeout: Duration, }, // We are waiting for TLS to install into the channel so that we can // proceed to return the SslStream. TlsAccepting { #[pin] future: Timeout< TlsAccept >, service: Option, } } } impl Future for OpenSSLAcceptorFuture where F: Future>, I: AsyncRead + AsyncWrite + Unpin, { type Output = io::Result<(SslStream, S)>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let mut this = self.project(); // The inner future here is what is doing the lower level accept, such as // our tcp socket. // // So we poll on that first, when it's ready we then swap our the inner future to // one waiting for our ssl layer to accept/install. // // Then once that's ready we can then wrap and provide the SslStream back out. // This loop exists to allow the Poll::Ready from InnerAccept on complete // to re-poll immediately. Otherwise all other paths are immediate returns. loop { match this.inner.as_mut().project() { AcceptFutureProj::InnerAccepting { future, handshake_timeout, } => match future.poll(cx) { Poll::Ready(Ok((stream, service))) => { let server_config = this.config.take().expect( "config is not set. this is a bug in axum-server, please report", ); // Change to poll::ready(err) let ssl = Ssl::new(server_config.get_inner().context()).unwrap(); let tls_stream = SslStream::new(ssl, stream).unwrap(); let future = TlsAccept { tls_stream: Some(tls_stream), }; let service = Some(service); let handshake_timeout = *handshake_timeout; this.inner.set(AcceptFuture::TlsAccepting { future: timeout(handshake_timeout, future), service, }); // the loop is now triggered to immediately poll on // ssl stream accept. } Poll::Ready(Err(e)) => return Poll::Ready(Err(e)), Poll::Pending => return Poll::Pending, }, AcceptFutureProj::TlsAccepting { future, service } => match future.poll(cx) { Poll::Ready(Ok(Ok(stream))) => { let service = service.take().expect("future polled after ready"); return Poll::Ready(Ok((stream, service))); } Poll::Ready(Ok(Err(e))) => return Poll::Ready(Err(e)), Poll::Ready(Err(timeout)) => { return Poll::Ready(Err(Error::new(ErrorKind::TimedOut, timeout))) } Poll::Pending => return Poll::Pending, }, } } } } programatik29-axum-server-f60b615/src/tls_openssl/mod.rs000066400000000000000000000364511476502433200233140ustar00rootroot00000000000000//! Tls implementation using [`openssl`] //! //! # Example //! //! ```rust,no_run //! use axum::{routing::get, Router}; //! use axum_server::tls_openssl::OpenSSLConfig; //! use std::net::SocketAddr; //! //! #[tokio::main] //! async fn main() { //! let app = Router::new().route("/", get(|| async { "Hello, world!" })); //! //! let config = OpenSSLConfig::from_pem_file( //! "examples/self-signed-certs/cert.pem", //! "examples/self-signed-certs/key.pem", //! ) //! .unwrap(); //! //! let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); //! println!("listening on {}", addr); //! axum_server::bind_openssl(addr, config) //! .serve(app.into_make_service()) //! .await //! .unwrap(); //! } //! ``` use self::future::OpenSSLAcceptorFuture; use crate::{ accept::{Accept, DefaultAcceptor}, server::Server, }; use arc_swap::ArcSwap; use openssl::{ pkey::PKey, ssl::{ self, AlpnError, Error as OpenSSLError, SslAcceptor, SslAcceptorBuilder, SslFiletype, SslMethod, SslRef, }, x509::X509, }; use std::{convert::TryFrom, fmt, net::SocketAddr, path::Path, sync::Arc, time::Duration}; use tokio::io::{AsyncRead, AsyncWrite}; use tokio_openssl::SslStream; pub mod future; /// Create a TLS server that will be bound to the provided socket with a configuration. See /// the [`crate::tls_openssl`] module for more details. pub fn bind_openssl(addr: SocketAddr, config: OpenSSLConfig) -> Server { let acceptor = OpenSSLAcceptor::new(config); Server::bind(addr).acceptor(acceptor) } /// Tls acceptor that uses OpenSSL. For details on how to use this see [`crate::tls_openssl`] module /// for more details. #[derive(Clone)] pub struct OpenSSLAcceptor { inner: A, config: OpenSSLConfig, handshake_timeout: Duration, } impl OpenSSLAcceptor { /// Create a new OpenSSL acceptor based on the provided [`OpenSSLConfig`]. This is /// generally used with manual calls to [`Server::bind`]. You may want [`bind_openssl`] /// instead. pub fn new(config: OpenSSLConfig) -> Self { let inner = DefaultAcceptor::new(); #[cfg(not(test))] let handshake_timeout = Duration::from_secs(10); // Don't force tests to wait too long. #[cfg(test)] let handshake_timeout = Duration::from_secs(1); Self { inner, config, handshake_timeout, } } /// Override the default TLS handshake timeout of 10 seconds. pub fn handshake_timeout(mut self, val: Duration) -> Self { self.handshake_timeout = val; self } } impl OpenSSLAcceptor { /// Overwrite inner acceptor. pub fn acceptor(self, acceptor: Acceptor) -> OpenSSLAcceptor { OpenSSLAcceptor { inner: acceptor, config: self.config, handshake_timeout: self.handshake_timeout, } } } impl Accept for OpenSSLAcceptor where A: Accept, A::Stream: AsyncRead + AsyncWrite + Unpin, { type Stream = SslStream; type Service = A::Service; type Future = OpenSSLAcceptorFuture; fn accept(&self, stream: I, service: S) -> Self::Future { let inner_future = self.inner.accept(stream, service); let config = self.config.clone(); OpenSSLAcceptorFuture::new(inner_future, config, self.handshake_timeout) } } impl fmt::Debug for OpenSSLAcceptor { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("OpenSSLAcceptor").finish() } } /// OpenSSL configuration. #[derive(Clone)] pub struct OpenSSLConfig { acceptor: Arc>, } impl OpenSSLConfig { /// Create config from `Arc<`[`SslAcceptor`]`>`. pub fn from_acceptor(acceptor: Arc) -> Self { let acceptor = Arc::new(ArcSwap::new(acceptor)); OpenSSLConfig { acceptor } } /// This helper will establish a TLS server based on strong cipher suites /// from a DER-encoded certificate and key. pub fn from_der(cert: &[u8], key: &[u8]) -> Result { let acceptor = Arc::new(ArcSwap::from_pointee(config_from_der(cert, key)?)); Ok(OpenSSLConfig { acceptor }) } /// This helper will establish a TLS server based on strong cipher suites /// from a PEM-formatted certificate and key. pub fn from_pem(cert: &[u8], key: &[u8]) -> Result { let acceptor = Arc::new(ArcSwap::from_pointee(config_from_pem(cert, key)?)); Ok(OpenSSLConfig { acceptor }) } /// This helper will establish a TLS server based on strong cipher suites /// from a PEM-formatted certificate and key. pub fn from_pem_file( cert: impl AsRef, key: impl AsRef, ) -> Result { let acceptor = Arc::new(ArcSwap::from_pointee(config_from_pem_file(cert, key)?)); Ok(OpenSSLConfig { acceptor }) } /// This helper will establish a TLS server based on strong cipher suites /// from a PEM-formatted certificate chain and key. pub fn from_pem_chain_file( chain: impl AsRef, key: impl AsRef, ) -> Result { let acceptor = Arc::new(ArcSwap::from_pointee(config_from_pem_chain_file( chain, key, )?)); Ok(OpenSSLConfig { acceptor }) } /// Get inner `Arc<`[`SslAcceptor`]`>`. pub fn get_inner(&self) -> Arc { self.acceptor.load_full() } /// Reload acceptor from `Arc<`[`SslAcceptor`]`>`. pub fn reload_from_acceptor(&self, acceptor: Arc) { self.acceptor.store(acceptor); } /// Reload acceptor from a DER-encoded certificate and key. pub fn reload_from_der(&self, cert: &[u8], key: &[u8]) -> Result<(), OpenSSLError> { let acceptor = Arc::new(config_from_der(cert, key)?); self.acceptor.store(acceptor); Ok(()) } /// Reload acceptor from a PEM-formatted certificate and key. pub fn reload_from_pem(&self, cert: &[u8], key: &[u8]) -> Result<(), OpenSSLError> { let acceptor = Arc::new(config_from_pem(cert, key)?); self.acceptor.store(acceptor); Ok(()) } /// Reload acceptor from a PEM-formatted certificate and key. pub fn reload_from_pem_file( &self, cert: impl AsRef, key: impl AsRef, ) -> Result<(), OpenSSLError> { let acceptor = Arc::new(config_from_pem_file(cert, key)?); self.acceptor.store(acceptor); Ok(()) } /// Reload acceptor from a PEM-formatted certificate chain and key. pub fn reload_from_pem_chain_file( &self, chain: impl AsRef, key: impl AsRef, ) -> Result<(), OpenSSLError> { let acceptor = Arc::new(config_from_pem_chain_file(chain, key)?); self.acceptor.store(acceptor); Ok(()) } } impl TryFrom for OpenSSLConfig { type Error = OpenSSLError; /// Build the [`OpenSSLConfig`] from an [`SslAcceptorBuilder`]. This allows precise /// control over the settings that will be used by OpenSSL in this server. /// /// # Example /// ``` /// use axum_server::tls_openssl::OpenSSLConfig; /// use openssl::ssl::{SslAcceptor, SslMethod}; /// use std::convert::TryFrom; /// /// #[tokio::main] /// async fn main() { /// let mut tls_builder = SslAcceptor::mozilla_modern_v5(SslMethod::tls()) /// .unwrap(); /// // Set configurations like set_certificate_chain_file or /// // set_private_key_file. /// // let tls_builder.set_ ... ; /// let _config = OpenSSLConfig::try_from(tls_builder); /// } /// ``` fn try_from(mut tls_builder: SslAcceptorBuilder) -> Result { // Any other checks? tls_builder.check_private_key()?; tls_builder.set_alpn_select_callback(alpn_select); let acceptor = Arc::new(ArcSwap::from_pointee(tls_builder.build())); Ok(OpenSSLConfig { acceptor }) } } impl fmt::Debug for OpenSSLConfig { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("OpenSSLConfig").finish() } } fn alpn_select<'a>(_tls: &mut SslRef, client: &'a [u8]) -> Result<&'a [u8], AlpnError> { ssl::select_next_proto(b"\x02h2\x08http/1.1", client).ok_or(AlpnError::NOACK) } fn config_from_der(cert: &[u8], key: &[u8]) -> Result { let cert = X509::from_der(cert)?; let key = PKey::private_key_from_der(key)?; let mut tls_builder = SslAcceptor::mozilla_modern_v5(SslMethod::tls())?; tls_builder.set_certificate(&cert)?; tls_builder.set_private_key(&key)?; tls_builder.check_private_key()?; tls_builder.set_alpn_select_callback(alpn_select); let acceptor = tls_builder.build(); Ok(acceptor) } fn config_from_pem(cert: &[u8], key: &[u8]) -> Result { let cert = X509::from_pem(cert)?; let key = PKey::private_key_from_pem(key)?; let mut tls_builder = SslAcceptor::mozilla_modern_v5(SslMethod::tls())?; tls_builder.set_certificate(&cert)?; tls_builder.set_private_key(&key)?; tls_builder.check_private_key()?; tls_builder.set_alpn_select_callback(alpn_select); let acceptor = tls_builder.build(); Ok(acceptor) } fn config_from_pem_file( cert: impl AsRef, key: impl AsRef, ) -> Result { let mut tls_builder = SslAcceptor::mozilla_modern_v5(SslMethod::tls())?; tls_builder.set_certificate_file(cert, SslFiletype::PEM)?; tls_builder.set_private_key_file(key, SslFiletype::PEM)?; tls_builder.check_private_key()?; tls_builder.set_alpn_select_callback(alpn_select); let acceptor = tls_builder.build(); Ok(acceptor) } fn config_from_pem_chain_file( chain: impl AsRef, key: impl AsRef, ) -> Result { let mut tls_builder = SslAcceptor::mozilla_modern_v5(SslMethod::tls())?; tls_builder.set_certificate_chain_file(chain)?; tls_builder.set_private_key_file(key, SslFiletype::PEM)?; tls_builder.check_private_key()?; tls_builder.set_alpn_select_callback(alpn_select); let acceptor = tls_builder.build(); Ok(acceptor) } #[cfg(test)] mod tests { use crate::{ handle::Handle, tls_openssl::{self, OpenSSLConfig}, }; use axum::body::Body; use axum::routing::get; use axum::Router; use bytes::Bytes; use http::{response, Request}; use http_body_util::BodyExt; use hyper::client::conn::http1::{handshake, SendRequest}; use hyper_util::rt::TokioIo; use std::{io, net::SocketAddr}; use tokio::{net::TcpStream, task::JoinHandle}; use openssl::{ ssl::{Ssl, SslConnector, SslMethod, SslVerifyMode}, x509::X509, }; use std::pin::Pin; use tokio_openssl::SslStream; #[tokio::test] async fn start_and_request() { let (_handle, _server_task, addr) = start_server().await; let (mut client, _conn) = connect(addr).await; let (_parts, body) = send_empty_request(&mut client).await; assert_eq!(body.as_ref(), b"Hello, world!"); } #[tokio::test] async fn test_reload() { let handle = Handle::new(); let config = OpenSSLConfig::from_pem_file( "examples/self-signed-certs/cert.pem", "examples/self-signed-certs/key.pem", ) .unwrap(); let server_handle = handle.clone(); let openssl_config = config.clone(); tokio::spawn(async move { let app = Router::new().route("/", get(|| async { "Hello, world!" })); let addr = SocketAddr::from(([127, 0, 0, 1], 0)); tls_openssl::bind_openssl(addr, openssl_config) .handle(server_handle) .serve(app.into_make_service()) .await }); let addr = handle.listening().await.unwrap(); let cert_a = get_first_cert(addr).await; let mut cert_b = get_first_cert(addr).await; assert_eq!(cert_a, cert_b); config .reload_from_pem_file( "examples/self-signed-certs/reload/cert.pem", "examples/self-signed-certs/reload/key.pem", ) .unwrap(); cert_b = get_first_cert(addr).await; assert_ne!(cert_a, cert_b); config .reload_from_pem_file( "examples/self-signed-certs/cert.pem", "examples/self-signed-certs/key.pem", ) .unwrap(); cert_b = get_first_cert(addr).await; assert_eq!(cert_a, cert_b); } async fn start_server() -> (Handle, JoinHandle>, SocketAddr) { let handle = Handle::new(); let server_handle = handle.clone(); let server_task = tokio::spawn(async move { let app = Router::new().route("/", get(|| async { "Hello, world!" })); let config = OpenSSLConfig::from_pem_file( "examples/self-signed-certs/cert.pem", "examples/self-signed-certs/key.pem", ) .unwrap(); let addr = SocketAddr::from(([127, 0, 0, 1], 0)); tls_openssl::bind_openssl(addr, config) .handle(server_handle) .serve(app.into_make_service()) .await }); let addr = handle.listening().await.unwrap(); (handle, server_task, addr) } async fn get_first_cert(addr: SocketAddr) -> X509 { let stream = TcpStream::connect(addr).await.unwrap(); let tls_stream = tls_connector(dns_name(), stream).await; tls_stream.ssl().peer_certificate().unwrap() } async fn connect(addr: SocketAddr) -> (SendRequest, JoinHandle<()>) { let stream = TcpStream::connect(addr).await.unwrap(); let tls_stream = TokioIo::new(tls_connector(dns_name(), stream).await); let (send_request, connection) = handshake(tls_stream).await.unwrap(); let task = tokio::spawn(async move { let _ = connection.await; }); (send_request, task) } async fn send_empty_request(client: &mut SendRequest) -> (response::Parts, Bytes) { let (parts, body) = client .send_request(Request::new(Body::empty())) .await .unwrap() .into_parts(); let body = body.collect().await.unwrap().to_bytes(); (parts, body) } async fn tls_connector(hostname: &str, stream: TcpStream) -> SslStream { let mut tls_parms = SslConnector::builder(SslMethod::tls_client()).unwrap(); tls_parms.set_verify(SslVerifyMode::NONE); let hostname_owned = hostname.to_string(); tls_parms.set_client_hello_callback(move |ssl_ref, _ssl_alert| { ssl_ref .set_hostname(hostname_owned.as_str()) .map(|()| openssl::ssl::ClientHelloResponse::SUCCESS) }); let tls_parms = tls_parms.build(); let ssl = Ssl::new(tls_parms.context()).unwrap(); let mut tls_stream = SslStream::new(ssl, stream).unwrap(); SslStream::connect(Pin::new(&mut tls_stream)).await.unwrap(); tls_stream } fn dns_name() -> &'static str { "localhost" } } programatik29-axum-server-f60b615/src/tls_rustls/000077500000000000000000000000001476502433200220275ustar00rootroot00000000000000programatik29-axum-server-f60b615/src/tls_rustls/future.rs000066400000000000000000000071451476502433200237160ustar00rootroot00000000000000//! Future types. use crate::tls_rustls::RustlsConfig; use pin_project_lite::pin_project; use std::io::{Error, ErrorKind}; use std::time::Duration; use std::{ fmt, future::Future, io, pin::Pin, task::{Context, Poll}, }; use tokio::io::{AsyncRead, AsyncWrite}; use tokio::time::{timeout, Timeout}; use tokio_rustls::{server::TlsStream, Accept, TlsAcceptor}; pin_project! { /// Future type for [`RustlsAcceptor`](crate::tls_rustls::RustlsAcceptor). pub struct RustlsAcceptorFuture { #[pin] inner: AcceptFuture, config: Option, } } impl RustlsAcceptorFuture { pub(crate) fn new(future: F, config: RustlsConfig, handshake_timeout: Duration) -> Self { let inner = AcceptFuture::Inner { future, handshake_timeout, }; let config = Some(config); Self { inner, config } } } impl fmt::Debug for RustlsAcceptorFuture { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("RustlsAcceptorFuture").finish() } } pin_project! { #[project = AcceptFutureProj] enum AcceptFuture { Inner { #[pin] future: F, handshake_timeout: Duration, }, Accept { #[pin] future: Timeout>, service: Option, }, } } impl Future for RustlsAcceptorFuture where F: Future>, I: AsyncRead + AsyncWrite + Unpin, { type Output = io::Result<(TlsStream, S)>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let mut this = self.project(); loop { match this.inner.as_mut().project() { AcceptFutureProj::Inner { future, handshake_timeout, } => { match future.poll(cx) { Poll::Ready(Ok((stream, service))) => { let server_config = this.config .take() .expect("config is not set. this is a bug in axum-server, please report") .get_inner(); let acceptor = TlsAcceptor::from(server_config); let future = acceptor.accept(stream); let service = Some(service); let handshake_timeout = *handshake_timeout; this.inner.set(AcceptFuture::Accept { future: timeout(handshake_timeout, future), service, }); } Poll::Ready(Err(e)) => return Poll::Ready(Err(e)), Poll::Pending => return Poll::Pending, } } AcceptFutureProj::Accept { future, service } => match future.poll(cx) { Poll::Ready(Ok(Ok(stream))) => { let service = service.take().expect("future polled after ready"); return Poll::Ready(Ok((stream, service))); } Poll::Ready(Ok(Err(e))) => return Poll::Ready(Err(e)), Poll::Ready(Err(timeout)) => { return Poll::Ready(Err(Error::new(ErrorKind::TimedOut, timeout))) } Poll::Pending => return Poll::Pending, }, } } } } programatik29-axum-server-f60b615/src/tls_rustls/mod.rs000066400000000000000000000472031476502433200231620ustar00rootroot00000000000000//! Tls implementation using [`rustls`]. //! //! # Example //! //! ```rust,no_run //! use axum::{routing::get, Router}; //! use axum_server::tls_rustls::RustlsConfig; //! use std::net::SocketAddr; //! //! #[tokio::main] //! async fn main() { //! let app = Router::new().route("/", get(|| async { "Hello, world!" })); //! //! let config = RustlsConfig::from_pem_file( //! "examples/self-signed-certs/cert.pem", //! "examples/self-signed-certs/key.pem", //! ) //! .await //! .unwrap(); //! //! let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); //! println!("listening on {}", addr); //! axum_server::bind_rustls(addr, config) //! .serve(app.into_make_service()) //! .await //! .unwrap(); //! } //! ``` use self::future::RustlsAcceptorFuture; use crate::{ accept::{Accept, DefaultAcceptor}, server::{io_other, Server}, }; use arc_swap::ArcSwap; use rustls::ServerConfig; use rustls_pemfile::Item; use rustls_pki_types::{CertificateDer, PrivateKeyDer}; use std::time::Duration; use std::{fmt, io, net::SocketAddr, path::Path, sync::Arc}; use tokio::{ io::{AsyncRead, AsyncWrite}, task::spawn_blocking, }; use tokio_rustls::server::TlsStream; pub(crate) mod export { #[allow(clippy::wildcard_imports)] use super::*; /// Create a tls server that will bind to provided address. #[cfg_attr(docsrs, doc(cfg(feature = "tls-rustls")))] pub fn bind_rustls(addr: SocketAddr, config: RustlsConfig) -> Server { super::bind_rustls(addr, config) } /// Create a tls server from existing `std::net::TcpListener`. #[cfg_attr(docsrs, doc(cfg(feature = "tls-rustls")))] pub fn from_tcp_rustls( listener: std::net::TcpListener, config: RustlsConfig, ) -> Server { let acceptor = RustlsAcceptor::new(config); Server::from_tcp(listener).acceptor(acceptor) } } pub mod future; /// Create a tls server that will bind to provided address. pub fn bind_rustls(addr: SocketAddr, config: RustlsConfig) -> Server { let acceptor = RustlsAcceptor::new(config); Server::bind(addr).acceptor(acceptor) } /// Create a tls server from existing `std::net::TcpListener`. pub fn from_tcp_rustls( listener: std::net::TcpListener, config: RustlsConfig, ) -> Server { let acceptor = RustlsAcceptor::new(config); Server::from_tcp(listener).acceptor(acceptor) } /// Tls acceptor using rustls. #[derive(Clone)] pub struct RustlsAcceptor { inner: A, config: RustlsConfig, handshake_timeout: Duration, } impl RustlsAcceptor { /// Create a new rustls acceptor. pub fn new(config: RustlsConfig) -> Self { let inner = DefaultAcceptor::new(); #[cfg(not(test))] let handshake_timeout = Duration::from_secs(10); // Don't force tests to wait too long. #[cfg(test)] let handshake_timeout = Duration::from_secs(1); Self { inner, config, handshake_timeout, } } /// Override the default TLS handshake timeout of 10 seconds, except during testing. pub fn handshake_timeout(mut self, val: Duration) -> Self { self.handshake_timeout = val; self } } impl RustlsAcceptor { /// Overwrite inner acceptor. pub fn acceptor(self, acceptor: Acceptor) -> RustlsAcceptor { RustlsAcceptor { inner: acceptor, config: self.config, handshake_timeout: self.handshake_timeout, } } } impl Accept for RustlsAcceptor where A: Accept, A::Stream: AsyncRead + AsyncWrite + Unpin, { type Stream = TlsStream; type Service = A::Service; type Future = RustlsAcceptorFuture; fn accept(&self, stream: I, service: S) -> Self::Future { let inner_future = self.inner.accept(stream, service); let config = self.config.clone(); RustlsAcceptorFuture::new(inner_future, config, self.handshake_timeout) } } impl fmt::Debug for RustlsAcceptor { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("RustlsAcceptor").finish() } } /// Rustls configuration. #[derive(Clone)] pub struct RustlsConfig { inner: Arc>, } impl RustlsConfig { /// Create config from `Arc<`[`ServerConfig`]`>`. /// /// NOTE: You need to set ALPN protocols (like `http/1.1` or `h2`) manually. pub fn from_config(config: Arc) -> Self { let inner = Arc::new(ArcSwap::new(config)); Self { inner } } /// Create config from DER-encoded data. /// /// The certificate must be DER-encoded X.509. /// /// The private key must be DER-encoded ASN.1 in either PKCS#8 or PKCS#1 format. pub async fn from_der(cert: Vec>, key: Vec) -> io::Result { let server_config = spawn_blocking(|| config_from_der(cert, key)) .await .unwrap()?; let inner = Arc::new(ArcSwap::from_pointee(server_config)); Ok(Self { inner }) } /// Create config from PEM formatted data. /// /// Certificate and private key must be in PEM format. pub async fn from_pem(cert: Vec, key: Vec) -> io::Result { let server_config = spawn_blocking(|| config_from_pem(cert, key)) .await .unwrap()?; let inner = Arc::new(ArcSwap::from_pointee(server_config)); Ok(Self { inner }) } /// Create config from PEM formatted files. /// /// Contents of certificate file and private key file must be in PEM format. pub async fn from_pem_file(cert: impl AsRef, key: impl AsRef) -> io::Result { let server_config = config_from_pem_file(cert, key).await?; let inner = Arc::new(ArcSwap::from_pointee(server_config)); Ok(Self { inner }) } /// Get inner `Arc<`[`ServerConfig`]`>`. pub fn get_inner(&self) -> Arc { self.inner.load_full() } /// Reload config from `Arc<`[`ServerConfig`]`>`. pub fn reload_from_config(&self, config: Arc) { self.inner.store(config); } /// Reload config from DER-encoded data. /// /// The certificate must be DER-encoded X.509. /// /// The private key must be DER-encoded ASN.1 in either PKCS#8 or PKCS#1 format. pub async fn reload_from_der(&self, cert: Vec>, key: Vec) -> io::Result<()> { let server_config = spawn_blocking(|| config_from_der(cert, key)) .await .unwrap()?; let inner = Arc::new(server_config); self.inner.store(inner); Ok(()) } /// This helper will establish a TLS server based on strong cipher suites /// from a PEM-formatted certificate chain and key. pub async fn from_pem_chain_file( chain: impl AsRef, key: impl AsRef, ) -> io::Result { let server_config = config_from_pem_chain_file(chain, key).await?; let inner = Arc::new(ArcSwap::from_pointee(server_config)); Ok(Self { inner }) } /// Reload config from PEM formatted data. /// /// Certificate and private key must be in PEM format. pub async fn reload_from_pem(&self, cert: Vec, key: Vec) -> io::Result<()> { let server_config = spawn_blocking(|| config_from_pem(cert, key)) .await .unwrap()?; let inner = Arc::new(server_config); self.inner.store(inner); Ok(()) } /// Reload config from PEM formatted files. /// /// Contents of certificate file and private key file must be in PEM format. pub async fn reload_from_pem_file( &self, cert: impl AsRef, key: impl AsRef, ) -> io::Result<()> { let server_config = config_from_pem_file(cert, key).await?; let inner = Arc::new(server_config); self.inner.store(inner); Ok(()) } } impl fmt::Debug for RustlsConfig { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("RustlsConfig").finish() } } fn config_from_der(cert: Vec>, key: Vec) -> io::Result { let cert = cert.into_iter().map(CertificateDer::from).collect(); let key = PrivateKeyDer::try_from(key).map_err(io_other)?; let mut config = ServerConfig::builder() .with_no_client_auth() .with_single_cert(cert, key) .map_err(io_other)?; config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; Ok(config) } fn config_from_pem(cert: Vec, key: Vec) -> io::Result { let cert = rustls_pemfile::certs(&mut cert.as_ref()) .map(|it| it.map(|it| it.to_vec())) .collect::, _>>()?; // Check the entire PEM file for the key in case it is not first section let mut key_vec: Vec> = rustls_pemfile::read_all(&mut key.as_ref()) .filter_map(|i| match i.ok()? { Item::Sec1Key(key) => Some(key.secret_sec1_der().to_vec()), Item::Pkcs1Key(key) => Some(key.secret_pkcs1_der().to_vec()), Item::Pkcs8Key(key) => Some(key.secret_pkcs8_der().to_vec()), _ => None, }) .collect(); // Make sure file contains only one key if key_vec.len() != 1 { return Err(io_other("private key format not supported")); } config_from_der(cert, key_vec.pop().unwrap()) } async fn config_from_pem_file( cert: impl AsRef, key: impl AsRef, ) -> io::Result { let cert = fs_err::tokio::read(cert.as_ref()).await?; let key = fs_err::tokio::read(key.as_ref()).await?; config_from_pem(cert, key) } async fn config_from_pem_chain_file( cert: impl AsRef, chain: impl AsRef, ) -> io::Result { let cert = fs_err::tokio::read(cert.as_ref()).await?; let cert = rustls_pemfile::certs(&mut cert.as_ref()) .map(|it| it.map(|it| CertificateDer::from(it.to_vec()))) .collect::, _>>()?; let key = fs_err::tokio::read(chain.as_ref()).await?; let key_cert: PrivateKeyDer = match rustls_pemfile::read_one(&mut key.as_ref())? .ok_or_else(|| io_other("could not parse pem file"))? { Item::Pkcs8Key(key) => Ok(key.into()), Item::Sec1Key(key) => Ok(key.into()), Item::Pkcs1Key(key) => Ok(key.into()), x => Err(io_other(format!( "invalid certificate format, received: {x:?}" ))), }?; ServerConfig::builder() .with_no_client_auth() .with_single_cert(cert, key_cert) .map_err(|_| io_other("invalid certificate")) } #[cfg(test)] mod tests { use crate::handle::Handle; use crate::tls_rustls::{self, RustlsConfig}; use axum::body::Body; use axum::routing::get; use axum::Router; use bytes::Bytes; use http::{response, Request}; use http_body_util::BodyExt; use hyper::client::conn::http1::{handshake, SendRequest}; use hyper_util::rt::TokioIo; use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}; use rustls::{ClientConfig, DigitallySignedStruct, Error, SignatureScheme}; use rustls_pki_types::{CertificateDer, ServerName, UnixTime}; use std::fmt::Debug; use std::{convert::TryFrom, io, net::SocketAddr, sync::Arc, time::Duration}; use tokio::time::sleep; use tokio::{net::TcpStream, task::JoinHandle}; use tokio_rustls::TlsConnector; #[tokio::test] async fn start_and_request() { let (_handle, _server_task, addr) = start_server().await; let (mut client, _conn) = connect(addr).await; let (_parts, body) = send_empty_request(&mut client).await; assert_eq!(body.as_ref(), b"Hello, world!"); } #[ignore] #[tokio::test] async fn tls_timeout() { let (handle, _server_task, addr) = start_server().await; assert_eq!(handle.connection_count(), 0); // We intentionally avoid driving a TLS handshake to completion. let _stream = TcpStream::connect(addr).await.unwrap(); sleep(Duration::from_millis(500)).await; assert_eq!(handle.connection_count(), 1); tokio::time::sleep(Duration::from_millis(1000)).await; // Timeout defaults to 1s during testing, and we have waited 1.5 seconds. assert_eq!(handle.connection_count(), 0); } #[tokio::test] async fn test_reload() { let handle = Handle::new(); let config = RustlsConfig::from_pem_file( "examples/self-signed-certs/cert.pem", "examples/self-signed-certs/key.pem", ) .await .unwrap(); let server_handle = handle.clone(); let rustls_config = config.clone(); tokio::spawn(async move { let app = Router::new().route("/", get(|| async { "Hello, world!" })); let addr = SocketAddr::from(([127, 0, 0, 1], 0)); tls_rustls::bind_rustls(addr, rustls_config) .handle(server_handle) .serve(app.into_make_service()) .await }); let addr = handle.listening().await.unwrap(); let cert_a = get_first_cert(addr).await; let mut cert_b = get_first_cert(addr).await; assert_eq!(cert_a, cert_b); config .reload_from_pem_file( "examples/self-signed-certs/reload/cert.pem", "examples/self-signed-certs/reload/key.pem", ) .await .unwrap(); cert_b = get_first_cert(addr).await; assert_ne!(cert_a, cert_b); config .reload_from_pem_file( "examples/self-signed-certs/cert.pem", "examples/self-signed-certs/key.pem", ) .await .unwrap(); cert_b = get_first_cert(addr).await; assert_eq!(cert_a, cert_b); } async fn start_server() -> (Handle, JoinHandle>, SocketAddr) { let handle = Handle::new(); let server_handle = handle.clone(); let server_task = tokio::spawn(async move { let app = Router::new().route("/", get(|| async { "Hello, world!" })); let config = RustlsConfig::from_pem_file( "examples/self-signed-certs/cert.pem", "examples/self-signed-certs/key.pem", ) .await?; let addr = SocketAddr::from(([127, 0, 0, 1], 0)); tls_rustls::bind_rustls(addr, config) .handle(server_handle) .serve(app.into_make_service()) .await }); let addr = handle.listening().await.unwrap(); (handle, server_task, addr) } async fn get_first_cert(addr: SocketAddr) -> CertificateDer<'static> { let stream = TcpStream::connect(addr).await.unwrap(); let tls_stream = tls_connector().connect(dns_name(), stream).await.unwrap(); let (_io, client_connection) = tls_stream.into_inner(); client_connection.peer_certificates().unwrap()[0].clone() } async fn connect(addr: SocketAddr) -> (SendRequest, JoinHandle<()>) { let stream = TcpStream::connect(addr).await.unwrap(); let tls_stream = TokioIo::new(tls_connector().connect(dns_name(), stream).await.unwrap()); let (send_request, connection) = handshake(tls_stream).await.unwrap(); let task = tokio::spawn(async move { let _ = connection.await; }); (send_request, task) } async fn send_empty_request(client: &mut SendRequest) -> (response::Parts, Bytes) { let (parts, body) = client .send_request(Request::new(Body::empty())) .await .unwrap() .into_parts(); let body = body.collect().await.unwrap().to_bytes(); (parts, body) } fn tls_connector() -> TlsConnector { #[derive(Debug)] struct NoVerify; impl ServerCertVerifier for NoVerify { fn verify_server_cert( &self, _end_entity: &CertificateDer, _intermediates: &[CertificateDer], _server_name: &ServerName, _ocsp_response: &[u8], _now: UnixTime, ) -> Result { Ok(ServerCertVerified::assertion()) } fn verify_tls12_signature( &self, _message: &[u8], _cert: &CertificateDer<'_>, _dss: &DigitallySignedStruct, ) -> Result { Ok(HandshakeSignatureValid::assertion()) } fn verify_tls13_signature( &self, _message: &[u8], _cert: &CertificateDer<'_>, _dss: &DigitallySignedStruct, ) -> Result { Ok(HandshakeSignatureValid::assertion()) } fn supported_verify_schemes(&self) -> Vec { vec![ SignatureScheme::RSA_PKCS1_SHA1, SignatureScheme::RSA_PKCS1_SHA256, SignatureScheme::RSA_PKCS1_SHA384, SignatureScheme::RSA_PKCS1_SHA512, SignatureScheme::RSA_PSS_SHA256, SignatureScheme::RSA_PSS_SHA384, SignatureScheme::RSA_PSS_SHA512, ] } } let mut client_config = ClientConfig::builder() .dangerous() .with_custom_certificate_verifier(Arc::new(NoVerify)) .with_no_client_auth(); client_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; TlsConnector::from(Arc::new(client_config)) } fn dns_name() -> ServerName<'static> { ServerName::try_from("localhost").unwrap() } #[tokio::test] async fn from_pem_file_not_found() { let err = RustlsConfig::from_pem_file( "examples/self-signed-certs/missing.pem", "examples/self-signed-certs/key.pem", ) .await .unwrap_err(); assert_eq!(err.kind(), io::ErrorKind::NotFound); assert_eq!(err.to_string(), "failed to read from file `examples/self-signed-certs/missing.pem`: No such file or directory (os error 2)"); let err = RustlsConfig::from_pem_file( "examples/self-signed-certs/cert.pem", "examples/self-signed-certs/missing.pem", ) .await .unwrap_err(); assert_eq!(err.kind(), io::ErrorKind::NotFound); assert_eq!(err.to_string(), "failed to read from file `examples/self-signed-certs/missing.pem`: No such file or directory (os error 2)"); } #[tokio::test] async fn from_pem_file_chain_file_not_found() { let err = RustlsConfig::from_pem_chain_file( "examples/self-signed-certs/missing.pem", "examples/self-signed-certs/key.pem", ) .await .unwrap_err(); assert_eq!(err.kind(), io::ErrorKind::NotFound); assert_eq!(err.to_string(), "failed to read from file `examples/self-signed-certs/missing.pem`: No such file or directory (os error 2)"); let err = RustlsConfig::from_pem_chain_file( "examples/self-signed-certs/cert.pem", "examples/self-signed-certs/missing.pem", ) .await .unwrap_err(); assert_eq!(err.kind(), io::ErrorKind::NotFound); assert_eq!(err.to_string(), "failed to read from file `examples/self-signed-certs/missing.pem`: No such file or directory (os error 2)"); } }