Axum multiple listeners

Hello everyone,

I'm very new to the Axum / Tower / Hyper frameworks and want to build a simple hello world example with the caveat that it should listen on two different ports at the same time.

These listeners should each have their own routes associated with them. I would like to do this as the program I'm building will listen on a public IP(HTTPS) for client requests, while listening on a different port on an internal IP for microservice communications (obviously with an extra layer of authentication on top of it).

However all the tutorials immediately jump to Axum::Serve() with a single listener and none of the multi listener examples seem to work.

Thank you for your advice.

Have you considered just running multiple axum::serve concurrently?

2 Likes

This isn't what you asked for, but it's very common to run things behind a reverse proxy, that handles stuff like TLS, logging, some security checks, whatever. So you could just have that proxy filter for the public routes.

Good suggestion, but Serve<tokio::net::TcpListener, Router, Router> isn't a future and as such can't be spawned by tokio::spawn(). Am I missing something obvious here?

Valid remark, but I believe that externalising these security measures to the reverse proxy adds maintenance overhead and could easily result in errors (forget to block a new API call, make a typo, ...).

Plus I would like to perform TLS client side authentication for the microservices communications, but not for the public API.

Serve<..> implements IntoFuture: Serve in axum::serve - Rust

You can run multiple services like this:


// axum = "0.8.1"
// tokio = { version = "1.43.0", features = ["full"] }
use axum::{Router, routing::get};

#[tokio::main]
async fn main() {
    let t0 = tokio::task::spawn(async move { serve(3000).await });
    let t1 = tokio::task::spawn(async move { serve(3001).await });
    let _ = tokio::join!(t0, t1);
}

async fn serve(port: u16) {
    let router = Router::new().route("/", get(|| async { "Hello, World!" }));
    let listener = tokio::net::TcpListener::bind(&format!("0.0.0.0:{port}"))
        .await
        .unwrap();
    axum::serve(listener, router).await.unwrap();
}

This isn't exactly what you want, because you said that the second service would have different layers (like authentication). So I guess you'd have serve1() and serve2() that create the two different services you need.

I believe tokio::spawn might not be IntoFuture-aware yet.

Ah indeed, perfect!

Easily understood example, I can definitely go from here. Thanks for all the assistance everyone.