7 releases

new 0.3.1 Feb 7, 2025
0.3.0 Jan 19, 2025
0.2.3 Jan 18, 2025
0.1.0 Dec 30, 2024

#584 in HTTP server

Download history 287/week @ 2024-12-29 69/week @ 2025-01-05 142/week @ 2025-01-12 306/week @ 2025-01-19 159/week @ 2025-01-26 223/week @ 2025-02-02

831 downloads per month
Used in authly-common

MIT license

26KB
361 lines

tower-server

Server utility for running hyper HTTP servers with tower-service.

Integrates with rustls and implements graceful shutdown using tokio_util/CancellationToken.


lib.rs:

High-level hyper server interfacing with tower-service.

Features:

  • rustls integration
  • Graceful shutdown using CancellationToken from tokio_util.
  • Optional connnection middleware for handling the remote address
  • Dynamic TLS reconfiguration without restarting server, for e.g. certificate rotation
  • Optional TLS connection middleware, for example for mTLS integration

Example usage using Axum with graceful shutdown:

#[cfg(feature = "signal")]
// Uses the built-in termination signal:
let shutdown_token = tower_server::signal::termination_signal();

#[cfg(not(feature = "signal"))]
// Configure the shutdown token manually:
let shutdown_token = tokio_util::sync::CancellationToken::default();

let server = tower_server::Builder::new("0.0.0.0:8080".parse().unwrap())
    .with_graceful_shutdown(shutdown_token)
    .bind()
    .await
    .unwrap();

server.serve(axum::Router::new()).await;

Example using connection middleware

#[derive(Clone)]
struct RemoteAddr(std::net::SocketAddr);

let server = tower_server::Builder::new("0.0.0.0:8080".parse().unwrap())
    .with_connection_middleware(|req, remote_addr| {
        req.extensions_mut().insert(RemoteAddr(remote_addr));
    })
    .bind()
    .await
    .unwrap();

server.serve(axum::Router::new()).await;

Example using TLS connection middleware

use rustls_pki_types::CertificateDer;
use hyper::body::Incoming;

#[derive(Clone)]
struct PeerCertMiddleware;

/// A request extension that includes the mTLS peer certificate
#[derive(Clone)]
struct PeerCertificate(CertificateDer<'static>);

impl tower_server::tls::TlsConnectionMiddleware for PeerCertMiddleware {
    type Data = Option<PeerCertificate>;

    /// Step 1: Extract data from the rustls server connection.
    /// At this stage of TLS handshake the http::Request doesn't yet exist.
    fn data(&self, connection: &rustls::ServerConnection) -> Self::Data {
        Some(PeerCertificate(connection.peer_certificates()?.first()?.clone()))
    }

    /// Step 2: The http::Request now exists, and the request extension can be injected.
    fn call(&self, req: &mut http::Request<Incoming>, data: &Option<PeerCertificate>) {
        if let Some(peer_certificate) = data {
            req.extensions_mut().insert(peer_certificate.clone());
        }
    }
}

let server = tower_server::Builder::new("0.0.0.0:443".parse().unwrap())
    .with_scheme(tower_server::Scheme::Https)
    .with_tls_connection_middleware(PeerCertMiddleware)
    .with_tls_config(
        rustls::server::ServerConfig::builder()
            // Instead of this, actually configure client authentication here:
            .with_no_client_auth()
            // just a compiling example for setting a cert resolver, replace this with your actual config:
            .with_cert_resolver(Arc::new(rustls::server::ResolvesServerCertUsingSni::new()))
    )
    .bind()
    .await
    .unwrap();

server.serve(axum::Router::new()).await;

Example using dynamically chaning TLS configuration

tls::TlsConfigurer is implemented for futures_util::stream::BoxStream of [Arc]ed rustls::server::ServerConfigs:

use futures_util::StreamExt;

let initial_tls_config = Arc::new(
    rustls::server::ServerConfig::builder()
        .with_no_client_auth()
        .with_cert_resolver(Arc::new(rustls::server::ResolvesServerCertUsingSni::new()))
);

let tls_config_rotation = futures_util::stream::unfold((), |_| async move {
    // renews after a fixed delay:
    tokio::time::sleep(Duration::from_secs(10)).await;

    // just for illustration purposes, replace with your own ServerConfig:
    let renewed_config = Arc::new(
        rustls::server::ServerConfig::builder()
            .with_no_client_auth()
            .with_cert_resolver(Arc::new(rustls::server::ResolvesServerCertUsingSni::new()))
    );

    Some((renewed_config, ()))
});

let server = tower_server::Builder::new("0.0.0.0:443".parse().unwrap())
    .with_scheme(tower_server::Scheme::Https)
    .with_tls_config(
        // takes the initial config, which resolves without delay,
        // chained together with the subsequent dynamic updates:
        futures_util::stream::iter([initial_tls_config])
            .chain(tls_config_rotation)
            .boxed()
    )
    .bind()
    .await
    .unwrap();

server.serve(axum::Router::new()).await;

Dependencies

~14–24MB
~446K SLoC