Serve static content with axum

One of Rust’s nice properties is producing statically linked binaries making deployment simple and straightforward. In some cases this is not enough and additional data is required for proper function, for example static data for web servers. With dependencies such as include_dir and mime_guess this is a piece of cake to integrate into axum though.

Using include_dir we first declare variable that represents the data currently located in the static directory:

use include_dir::{include_dir, Dir};

static STATIC_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/static");

Now we define the static data route, passing *path to denote we want to match the entire remaining path.

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let app = axum::Router::new()
        .route("/static/*path", get(static_path));

    let addr = std::net::SocketAddr::from(([0, 0, 0, 0], 3000));

    axum::Server::bind(&addr)
        .serve(app.into_make_service())
        .await?;

    Ok(())
}

Note that we cannot use the newly added typed path functionality in axum-extra. Now onto the actual route handler:

async fn static_path(Path(path): Path<String>) -> impl IntoResponse {
    let path = path.trim_start_matches('/');
    let mime_type = mime_guess::from_path(path).first_or_text_plain();

    match STATIC_DIR.get_file(path) {
        None => Response::builder()
            .status(StatusCode::NOT_FOUND)
            .body(body::boxed(Empty::new()))
            .unwrap(),
        Some(file) => Response::builder()
            .status(StatusCode::OK)
            .header(
                header::CONTENT_TYPE,
                HeaderValue::from_str(mime_type.as_ref()).unwrap(),
            )
            .body(body::boxed(Full::from(file.contents())))
            .unwrap(),
    }
}

As you can see we first strip the initial slash and then use the mime_guess crate to guess a MIME type from it. If we are not able to do so, just assume text/plain however wrong that is. Then we try to locate the file path and either return a 404 or a 200 with the actual file contents. Easy as pie.