Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Embed Static File

Beside the HTML we might want to server some other static content. For example CSS, JavaScript, or even images. We can embed the content that we would like to serve in the Rust source code and we can create a route setting the Content-Type to the proper value.

[package]
name = "embed-static-file"
version = "0.1.0"
edition = "2024"
publish = false

[dependencies]
axum = "0.8.8"
tokio = { version = "1.50.0", features = ["full"] }

[dev-dependencies]
headers = "0.4.1"
http-body-util = "0.1.3"
tower = { version = "0.5.3", features = ["util"] }

Code

use axum::{
    Router,
    http::header::{self, HeaderMap},
    response::{Html, IntoResponse},
    routing::get,
};

async fn handle_main_page() -> Html<&'static str> {
    Html(
        r#"
<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="/static/css/style.css">
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <title>Embedded static file</title>
  </head>
  <body>
    <h1>Hello, World!</h1>
  </body>
</html>
"#,
    )
}

async fn send_style_css() -> impl IntoResponse {
    let css = r#"
h1 {
    color: blue;
}
    "#;
    let mut headers = HeaderMap::new();
    headers.insert(header::CONTENT_TYPE, "text/css".parse().unwrap());
    (headers, css)
}

fn create_router() -> Router {
    Router::new()
        .route("/", get(handle_main_page))
        .route("/static/css/style.css", get(send_style_css))
}

#[tokio::main]
async fn main() {
    let app = create_router();

    let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
        .await
        .unwrap();
    println!("listening on http://{}", listener.local_addr().unwrap());
    axum::serve(listener, app).await.unwrap();
}

#[cfg(test)]
mod tests;

Tests

#![allow(unused)]
fn main() {
use axum::{body::Body, http::Request, http::StatusCode};
use http_body_util::BodyExt;
use tower::ServiceExt;

use super::*;

#[tokio::test]
async fn test_main_page() {
    let response = create_router()
        .oneshot(Request::builder().uri("/").body(Body::empty()).unwrap())
        .await
        .unwrap();

    assert_eq!(response.status(), StatusCode::OK);

    let content_type = response.headers().get("content-type").unwrap();
    assert_eq!(content_type.to_str().unwrap(), "text/html; charset=utf-8");

    let body = response.into_body();
    let bytes = body.collect().await.unwrap().to_bytes();
    let html = String::from_utf8(bytes.to_vec()).unwrap();

    assert!(html.contains("<h1>Hello, World!</h1>"));
}

#[tokio::test]
async fn test_css() {
    let response = create_router()
        .oneshot(
            Request::builder()
                .uri("/static/css/style.css")
                .body(Body::empty())
                .unwrap(),
        )
        .await
        .unwrap();

    assert_eq!(response.status(), StatusCode::OK);

    let content_type = response.headers().get("content-type").unwrap();
    assert_eq!(content_type.to_str().unwrap(), "text/css");

    let body = response.into_body();
    let bytes = body.collect().await.unwrap().to_bytes();
    let content = String::from_utf8(bytes.to_vec()).unwrap();

    let expected = r#"
h1 {
    color: blue;
}
    "#;

    assert_eq!(content, expected);
}
}