programatik29 / axum-server

High level server designed to be used with axum framework.
MIT License
177 stars 63 forks source link

Listen on multiple sockets #66

Open jgraichen opened 1 year ago

jgraichen commented 1 year ago

I really do like the easy of working with axum and axum-server. It is straightforward to set up TLS and reload it, which is awesome when used with certificates that need to renewed often.

I would be genuinely interested in binding a server to multiple sockets of different types. For example, to accept an arbitrary list of TCP and unix domain sockets via systemd socket activation:

    let tls_config = RustlsConfig::from_pem_file(
        "examples/self-signed-certs/cert.pem",
        "examples/self-signed-certs/key.pem",
    )
    .await?;

    let server = axum_server::new();

    let mut lfd = ListenFd::from_env();
    if lfd.len() > 0 {
        for idx in 0..lfd.len() {
            if let Ok(Some(lst)) = lfd.take_tcp_listener(idx) {
                server = server.from_tcp_rustls(lst, tls_config);
            } else if let Ok(Some(lst)) = lfd.take_unix_listener(idx) {
                server = server.from_uds(lst);
            } else {
                warn!("Unsupported socket type at index {:?}", idx)
            }
        }
    } else {
        server = server.bind("[::]:3000".parse()?)
    }

    let app = Router::new().route("/", get(|| async { "Hello World" }));
    server.serve(app.into_make_service()).await?;

Having the same app listening on multiple sockets greatly improves usability and integration with many systems, such as systemd. Unix domain sockets are often used to provide a service to other local services, such as reverse proxies or load balancers. A second TCP listener is often spawned e.g. for debugging or monitoring purposes.

Does this feature sound reasonable for axum-server?

programatik29 commented 1 year ago

This feature makes sense but can't be done without sacrificing integration with axum. Currently, for axum to extract SocketAddr, AddrStream from hyper must be used (see docs.rs page).

I have been delaying answering because I think this would be a good addition to new upcoming server in hyper-util (which will ideally make this crate obsolete). axum will certainly work with that new server since it will be the "official" one.

Diggsey commented 5 months ago

That's not the case - in fact prior to hyper 1.x it was possible to do exactly this via acceptors.

For example, here's some code from my own application:

// Allow one of several different connection types to be used.
pub enum AnyConnection {
    // Direct TCP connection
    AddrStream(AddrStream),
    // TLS connection
    TlsStream(TlsStream),
    // Ngrok connection
    NgrokConn(ngrok::Conn),
}

// Allow getting the remote address from the underlying connection
impl Connected<&AnyConnection> for SocketAddr {
    fn connect_info(target: &AnyConnection) -> Self {
        match target {
            AnyConnection::AddrStream(inner) => inner.remote_addr(),
            AnyConnection::TlsStream(inner) => inner.io().expect("Stream errored").remote_addr(),
            AnyConnection::NgrokConn(inner) => inner.remote_addr(),
        }
    }
}

Which made it super easy to accept connections from any number of places. Unfortunately the new Accept trait from this crate is much more restrictive than the old Accept trait used to be, and it seems impossible to get the same behaviour.