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 external Static File

It is probably much better if we keep the CSS file outside of the Rust file and embed its content during compilation using the include_str! macro.

There is also an include_bytes! macro for embeddig images and other binary files.

[package]
name = "embed-external-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 = include_str!("static/style.css");
    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;

CSS file


h1 {
    color: green;
}

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: green;
}
"#;

    assert_eq!(content, expected);
}
}