Configuration with state
If we have some static data that we would like to make available in (some of) the routes, the best thing might be to
load it up-front and then pass it on as State.
We can do this by adding the object to the application using the with_state and then in the routes where would like to access this value
we put it in the list of parameters of the function handling the route.
We use Arc as it is a thread-safe reference-counting pointer. So the data will not be copied to each one of the threads handling the requests.
[package]
name = "hello-html-world"
version = "0.1.0"
edition = "2024"
publish = false
[dependencies]
axum = "0.8.8"
serde = { version = "1", features = ["derive"] }
tokio = { version = "1.50.0", features = ["full"] }
toml = "0.8"
[dev-dependencies]
headers = "0.4.1"
http-body-util = "0.1.3"
tower = { version = "0.5.3", features = ["util"] }
name = "axum maximus"
use axum::{Router, extract::State, response::Html, routing::get};
use serde::Deserialize;
use std::sync::Arc;
#[derive(Deserialize)]
struct Config {
name: String,
}
async fn handle_main_page(State(config): State<Arc<Config>>) -> Html<String> {
Html(format!("<h1>Hello, {}!</h1>", config.name))
}
fn create_router(config: Arc<Config>) -> Router {
Router::new()
.route("/", get(handle_main_page))
.with_state(config)
}
#[tokio::main]
async fn main() {
let config_content =
std::fs::read_to_string("config.toml").expect("Failed to read config.toml");
let config: Config = toml::from_str(&config_content).expect("Failed to parse config.toml");
let config = Arc::new(config);
let app = create_router(config);
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;
#![allow(unused)]
fn main() {
use axum::{body::Body, http::Request, http::StatusCode};
use http_body_util::BodyExt;
use tower::ServiceExt;
use super::*;
fn test_config() -> Arc<Config> {
Arc::new(Config {
name: String::from("test name"),
})
}
#[tokio::test]
async fn test_main_page() {
let response = create_router(test_config())
.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_eq!(html, "<h1>Hello, test name!</h1>");
}
#[tokio::test]
async fn test_missing_page() {
let response = create_router(test_config())
.oneshot(
Request::builder()
.uri("/other")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::NOT_FOUND);
// No content type
assert!(response.headers().get("content-type").is_none());
let body = response.into_body();
let bytes = body.collect().await.unwrap().to_bytes();
let html = String::from_utf8(bytes.to_vec()).unwrap();
assert_eq!(html, "");
}
}