axum-server icon indicating copy to clipboard operation
axum-server copied to clipboard

Listen on multiple sockets

Open jgraichen opened this issue 2 years ago • 2 comments
trafficstars

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?

jgraichen avatar Dec 02 '22 21:12 jgraichen

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.

programatik29 avatar Mar 11 '23 17:03 programatik29

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.

Diggsey avatar Apr 22 '24 16:04 Diggsey