axum-server

repository·master·Indexed 42 days ago

https://github.com/programatik29/axum-server

A high-performance HTTP server implementation built on hyper and optimized for the axum web framework. Version 0.8.0 supports HTTP/1 and HTTP/2, HTTPS via rustls or OpenSSL, and provides lifecycle management through a Handle for graceful shutdowns. It requires Rust 1.82+ and is written in 100% safe Rust.

Tokens
6.4K
Snippets
22
Records
30
Agent score
53%

What's inside axum-server

  1. Overview of axum-server features

    master

    axum-server is a high-performance [hyper] server implementation specifically designed for the [axum] framework. Key features include:

    • Support for HTTP/1 and HTTP/2.
    • HTTPS support via [rustls].
    • High performance powered by [hyper].
    • Uses [tower] for the service API.
    • High compatibility with [axum], designed to work with future releases.
  2. Use Watcher to track connection lifecycles

    master

    A Watcher<A> is an RAII-based guard used to track active connections. When a Watcher is created (typically via handle.watcher()), it increments the server's connection count. When the Watcher is dropped, the count is decremented.

    This mechanism is used internally to coordinate graceful shutdowns, ensuring the server knows when all active connection tasks have completed.

  3. How axum-server and Axum work together

    master
    The axum-server crate is designed to serve items that implement the tower::MakeService trait. Because Axum's Router is designed to be compatible with tower, you can easily bridge the two by calling .into_make_service() on an Axum Router. This makes axum-server a convenient high-performance wrapper for Axum applications using hyper.
  4. Manage server lifecycle with `Handle`

    master

    The Handle object allows you to control the server's lifecycle from outside the serve() loop. You can attach a handle to your server using .handle(handle) before calling .serve().

    Key capabilities:

    • Graceful Shutdown: Call handle.graceful_shutdown(Some(Duration)) to stop accepting new connections and wait for existing requests to finish within a specified timeout. If None is provided, it waits indefinitely.
    • Immediate Shutdown: Call handle.shutdown() to stop the server immediately.
    • Listening Status: Use handle.listening().await to wait until the server has successfully bound to the address and is ready to accept connections.
    use axum_server::{bind, Handle};
    use axum::{routing::get, Router};
    use std::time::Duration;
    
    let handle = Handle::new();
    let app = Router::new().route("/", get(|| async { "Hello" }));
    let addr = "127.0.0.1:3000".parse().unwrap();
    
    // Spawn the server in a background task
    let server_task = tokio::spawn(async move {
        bind(addr)
            .handle(handle.clone())
            .serve(app.into_make_service())
            .await
    });
    
    // Wait until the server is actually listening
    let listening_addr = handle.listening().await.unwrap();
    println!("Listening on {}", listening_addr);
    
    // Perform graceful shutdown after some time
    tokio::time::sleep(Duration::from_secs(5)).await;
    handle.graceful_shutdown(Some(Duration::from_secs(10)));
    
    server_task.await.unwrap().unwrap();
  5. Configure HTTPS with Rustls

    master
    If the tls-rustls or tls-rustls-no-provider feature is enabled, you can create an HTTPS server using bind_rustls. This requires providing a RustlsConfig. RustlsConfig can be cloned, and its reload methods can be used on the clone to perform live TLS configuration reloads.
  6. Reload TLS certificates at runtime

    master

    One of the key features of OpenSSLConfig is the ability to reload certificates and keys without dropping the server connection. This is useful for certificate rotation.

    Use the following methods on an existing OpenSSLConfig instance:

    • reload_from_pem_file(cert, key)
    • reload_from_pem_chain_file(chain, key)
    • reload_from_pem(cert, key)
    • reload_from_der(cert, key)
    • reload_from_acceptor(Arc<SslAcceptor>)
    // Assuming 'config' is an existing OpenSSLConfig used by a running server
    config.reload_from_pem_file("new_cert.pem", "new_key.pem").unwrap();
  7. Run a simple Axum application with axum-server

    master

    To serve an Axum Router using axum-server, use the axum_server::bind function. This function takes a SocketAddr and returns a builder that allows you to call .serve() with a service created from your Axum router via .into_make_service().

    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();
    }
  8. Configure OpenSSL with OpenSSLConfig

    master

    The OpenSSLConfig struct manages the TLS acceptor and supports hot-reloading of certificates without restarting the server. You can initialize it from various formats:

    • PEM files: from_pem_file(cert, key) or from_pem_chain_file(chain, key)
    • PEM bytes: from_pem(cert, key)
    • DER bytes: from_der(cert, key)
    • Existing Acceptor: from_acceptor(Arc<SslAcceptor>)
    • Builder: try_from(SslAcceptorBuilder) for fine-grained control over OpenSSL settings.

    All configuration methods use strong cipher suites (Mozilla modern v5) and support ALPN for HTTP/2 and HTTP/1.1.

    // Example: Creating config from PEM files
    let config = OpenSSLConfig::from_pem_file("cert.pem", "key.pem").unwrap();
    
    // Example: Creating config from an OpenSSL SslAcceptorBuilder for custom settings
    use openssl::ssl::{SslAcceptorBuilder, SslMethod};
    use std::convert::TryFrom;
    
    let mut tls_builder = SslAcceptorBuilder::mozilla_modern_v5(SslMethod::tls()).unwrap();
    // ... customize tls_builder ...
    let config = OpenSSLConfig::try_from(tls_builder).unwrap();
  9. Serve a simple Axum application

    master

    To serve an Axum application, use the axum_server::bind function with a SocketAddr and call .serve() on the resulting server. You must convert your Axum Router into a service using .into_make_service() to satisfy the required trait bounds.

    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();
    }
  10. Implement the `Accept` trait to modify incoming connections

    master

    The Accept<I, S> trait allows you to intercept and modify the IO stream and the service produced during the connection acceptance phase. This is useful for applying configurations (like socket options) or wrapping the service before it is handed off to the server.

    To implement it, you must define:

    • Stream: The type of the IO stream (e.g., TcpStream).
    • Service: The type of the service produced.
    • Future: An asynchronous operation that returns io::Result<(Self::Stream, Self::Service)>.
    use axum_server::Accept;
    use std::io;
    use tokio::net::TcpStream;
    
    struct MyCustomAcceptor;
    
    impl<S> Accept<TcpStream, S> for MyCustomAcceptor {
        type Stream = TcpStream;
        type Service = S;
        type Future = std::future::Ready<io::Result<(Self::Stream, Self::Service)>>;
    
        fn accept(&self, stream: TcpStream, service: S) -> Self::Future {
            // Perform custom logic here, e.g., setting socket options
            std::future::ready(Ok((stream, service)))
        }
    }