Serve static data from the binary

For very small (micro) services it can come in handy to just distribute a single binary containing both code and data such as CSS or Javascript and avoid the hassle of dealing with paths, permissions, deployment etc. In this example we use the handy include_dir crate to bundle a directory of data within the compiled binary and the mime_guess crate to guess a MIME type based on the served file.

Dependencies

[dependencies]
axum = "0.5"
include_dir = "0"
mime_guess = "2"
tokio = { version = "1", features = ["full"] }

Code

First of all create a static directory next to the src directory and add this sample CSS file foo.css:

body {
    background-color: #ccc;
}

The interesting part of the code are the route which matches any path prefixed /static and the handler. In the handler we first strip the initial slash (because that would not match with get_file) and then just try to load the file. If we have a match, we try to guess a suitable MIME type and return it, otherwise we just return a 404:

use axum::body::{self, Empty, Full};
use axum::extract::Path;
use axum::http::{header, HeaderValue, StatusCode};
use axum::response::{IntoResponse, Response};
use axum::routing::get;
use axum::{Router, Server};
use include_dir::{include_dir, Dir};

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

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(),
    }
}

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

    Server::bind(&"0.0.0.0:8080".parse()?)
        .serve(app.into_make_service())
        .await?;

    Ok(())
}

Run

Start the server with

cargo run --bin serve-static-from-binary

and it will serve /static/foo.css as expected

$ curl http://127.0.0.1:8080/static/foo.css
body {
    background-color: #ccc;
}