Hostname based router with axum in Rust

axum's router does not natively support hostname-based routing, which may be useful if you need to serve multiple applications with a single server.

Fortunately, it's easy to compose multiple Routers to route your requests depending on the Host header.

Here is how.

Host based routing

First, we define our two sub-routers:

use axum::{
    body::Body, extract::Host, http::Request, response::Html, routing::any, Extension, Router,
};
use std::sync::Arc;
use std::{
    net::SocketAddr,
    sync::atomic::{AtomicUsize, Ordering},
};
use tower::ServiceExt;

struct State {
    api_requests: AtomicUsize,
    website_requests: AtomicUsize,
}

#[tokio::main]
async fn main() {
    let api_router = Router::new().route("/*path", any(api_handler));
    let website_router = Router::new().route("/*path", any(website_handler));

    // ...
}

async fn api_handler(Extension(state): Extension<Arc<State>>) -> Html<String> {
    state.api_requests.fetch_add(1, Ordering::SeqCst);
    Html(format!(
        "api: {}",
        state.api_requests.load(Ordering::SeqCst)
    ))
}

async fn website_handler(Extension(state): Extension<Arc<State>>) -> Html<String> {
    state.website_requests.fetch_add(1, Ordering::SeqCst);
    Html(format!(
        "website: {}",
        state.website_requests.load(Ordering::SeqCst)
    ))
}

Then we define the main router, which handles all the requests and dispatches them to the good sub-router depending on the hostname.

For that, we use the Host extractor and pattern matching:

#[tokio::main]
async fn main() {
    // ...
    let app = Router::new()
        .route(
            "/*path",
            any(|Host(hostname): Host, request: Request<Body>| async move {
                match hostname.as_str() {
                    "api.mydomain.com" => api_router.oneshot(request).await,
                    _ => website_router.oneshot(request).await,
                }
            }),
        )
        .layer(Extension(state));
    // ...
}

The great thing is that we can attach layers (middlewares and extensions) to the main router and they will be forwarded to the sub-routers.

Finally, we can run the server:

#[tokio::main]
async fn main() {

    // ...
    let addr = SocketAddr::from(([127, 0, 0, 1], 8080));
    println!("listening on {}", addr);
    axum::Server::bind(&addr)
        .serve(app.into_make_service())
        .await
        .unwrap();
}

The code is on GitHub

As usual, you can find the complete code on GitHub: github.com/skerkour/kerkour.com (please don't forget to star the repo 🙏).

Want to learn more? Get my book Black Hat Rust where we build multiple web applications in Rust to phish credentials and control a RAT (Remote Access Tool).

1 email / week to learn how to (ab)use technology for fun & profit: Programming, Hacking & Entrepreneurship.
I hate spam even more than you do. I'll never share your email, and you can unsubscribe at any time.

Tags: programming, rust, tutorial

Want to learn Rust, Cryptography and Security? Get my book Black Hat Rust!