AppBuilder for Axum

This commit is contained in:
Martin Berg Alstad 2024-06-26 10:20:52 +02:00
parent d5974dda20
commit 83f85f1938
3 changed files with 290 additions and 3 deletions

136
Cargo.lock generated
View File

@ -98,6 +98,12 @@ dependencies = [
"rustc-demangle", "rustc-demangle",
] ]
[[package]]
name = "bitflags"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.6.0" version = "1.6.0"
@ -270,9 +276,15 @@ version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]] [[package]]
name = "lib" name = "lib"
version = "1.0.0" version = "1.1.0"
dependencies = [ dependencies = [
"axum", "axum",
"derive", "derive",
@ -280,6 +292,10 @@ dependencies = [
"serde", "serde",
"tokio", "tokio",
"tokio-util", "tokio-util",
"tower",
"tower-http",
"tracing",
"tracing-subscriber",
] ]
[[package]] [[package]]
@ -348,6 +364,16 @@ dependencies = [
"minimal-lexical", "minimal-lexical",
] ]
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
dependencies = [
"overload",
"winapi",
]
[[package]] [[package]]
name = "object" name = "object"
version = "0.36.0" version = "0.36.0"
@ -363,6 +389,12 @@ version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "overload"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.3.1" version = "2.3.1"
@ -490,6 +522,15 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "sharded-slab"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
dependencies = [
"lazy_static",
]
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.13.2" version = "1.13.2"
@ -529,6 +570,16 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394"
[[package]]
name = "thread_local"
version = "1.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
dependencies = [
"cfg-if",
"once_cell",
]
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.38.0" version = "1.38.0"
@ -584,6 +635,23 @@ dependencies = [
"tracing", "tracing",
] ]
[[package]]
name = "tower-http"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5"
dependencies = [
"bitflags",
"bytes",
"http",
"http-body",
"http-body-util",
"pin-project-lite",
"tower-layer",
"tower-service",
"tracing",
]
[[package]] [[package]]
name = "tower-layer" name = "tower-layer"
version = "0.3.2" version = "0.3.2"
@ -604,9 +672,21 @@ checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
dependencies = [ dependencies = [
"log", "log",
"pin-project-lite", "pin-project-lite",
"tracing-attributes",
"tracing-core", "tracing-core",
] ]
[[package]]
name = "tracing-attributes"
version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "tracing-core" name = "tracing-core"
version = "0.1.32" version = "0.1.32"
@ -614,6 +694,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
dependencies = [ dependencies = [
"once_cell", "once_cell",
"valuable",
]
[[package]]
name = "tracing-log"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
dependencies = [
"log",
"once_cell",
"tracing-core",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
dependencies = [
"nu-ansi-term",
"sharded-slab",
"smallvec",
"thread_local",
"tracing-core",
"tracing-log",
] ]
[[package]] [[package]]
@ -622,12 +728,40 @@ version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "valuable"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.11.0+wasi-snapshot-preview1" version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.48.0" version = "0.48.0"

View File

@ -1,6 +1,6 @@
[package] [package]
name = "lib" name = "lib"
version = "1.0.0" version = "1.1.0"
edition = "2021" edition = "2021"
authors = ["Martin Berg Alstad"] authors = ["Martin Berg Alstad"]
@ -9,9 +9,14 @@ authors = ["Martin Berg Alstad"]
[dependencies] [dependencies]
# Api # Api
axum = { version = "0.7.5", optional = true } axum = { version = "0.7.5", optional = true }
tower = { version = "0.4.13", optional = true }
tower-http = { version = "0.5.2", optional = true, features = ["trace", "cors", "normalize-path"] }
# Async # Async
tokio = { version = "1.38.0", optional = true, features = ["fs"] } tokio = { version = "1.38.0", optional = true, features = ["fs"] }
tokio-util = { version = "0.7.11", optional = true, features = ["io"] } tokio-util = { version = "0.7.11", optional = true, features = ["io"] }
# Logging
tracing = "0.1.40"
tracing-subscriber = "0.3.18"
# Parsing # Parsing
nom = { version = "7.1.3", optional = true } nom = { version = "7.1.3", optional = true }
# Serialization / Deserialization # Serialization / Deserialization
@ -20,7 +25,7 @@ serde = { version = "1.0.203", optional = true, features = ["derive"] }
derive = { path = "derive", optional = true } derive = { path = "derive", optional = true }
[features] [features]
axum = ["dep:axum"] axum = ["dep:axum", "dep:tower", "dep:tower-http"]
tokio = ["dep:tokio", "dep:tokio-util"] tokio = ["dep:tokio", "dep:tokio-util"]
vec = [] vec = []
nom = ["dep:nom"] nom = ["dep:nom"]

View File

@ -1,3 +1,20 @@
#[cfg(feature = "axum")]
use {
axum::{extract::Request, handler::Handler, Router, ServiceExt},
std::net::Ipv4Addr,
tower::layer::Layer,
tower_http::{
cors::CorsLayer,
normalize_path::NormalizePathLayer,
trace,
trace::{HttpMakeClassifier, TraceLayer},
},
tracing::{info, Level},
};
#[cfg(all(feature = "axum", feature = "tokio"))]
use {std::io, std::net::SocketAddr, tokio::net::TcpListener};
// TODO trim trailing slash into macro > let _app = NormalizePathLayer::trim_trailing_slash().layer(create_app!(routes));
#[macro_export] #[macro_export]
#[cfg(feature = "axum")] #[cfg(feature = "axum")]
macro_rules! create_app { macro_rules! create_app {
@ -9,10 +26,141 @@ macro_rules! create_app {
}; };
} }
#[derive(Default)]
#[cfg(feature = "axum")]
pub struct AppBuilder {
router: Router,
socket: Option<(Ipv4Addr, u16)>,
cors: Option<CorsLayer>,
normalize_path: Option<bool>,
tracing: Option<TraceLayer<HttpMakeClassifier>>,
}
#[cfg(all(feature = "axum", feature = "tokio"))]
impl AppBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn routes(mut self, routes: &[Router]) -> Self {
self.router = routes.iter().cloned().fold(self.router, Router::merge);
self
}
pub fn socket(mut self, socket: impl Into<(Ipv4Addr, u16)>) -> Self {
self.socket = Some(socket.into());
self
}
pub fn fallback<H, T>(mut self, fallback: H) -> Self
where
H: Handler<T, ()>,
T: 'static,
{
self.router = self.router.fallback(fallback);
self
}
pub fn cors(mut self, cors: CorsLayer) -> Self {
self.cors = Some(cors);
self
}
pub fn normalize_path(mut self, normalize_path: bool) -> Self {
self.normalize_path = Some(normalize_path);
self
}
pub fn tracing(mut self, tracing: TraceLayer<HttpMakeClassifier>) -> Self {
self.tracing = Some(tracing);
self
}
pub async fn serve(self) -> io::Result<()> {
let listener = self.listener().await?;
let _ = fmt_trace();
if self.normalize_path.unwrap_or(true) {
let app = NormalizePathLayer::trim_trailing_slash().layer(self.create_app());
axum::serve(listener, ServiceExt::<Request>::into_make_service(app)).await?;
} else {
let app = self.create_app();
axum::serve(listener, app.into_make_service()).await?;
};
Ok(())
}
async fn listener(&self) -> io::Result<TcpListener> {
let addr = SocketAddr::from(self.socket.unwrap_or((Ipv4Addr::UNSPECIFIED, 8000)));
info!("Initializing server on: {addr}");
TcpListener::bind(&addr).await
}
fn create_app(self) -> Router {
let mut app = self.router;
if let Some(cors) = self.cors {
app = app.layer(cors);
}
app.layer(
self.tracing.unwrap_or(
TraceLayer::new_for_http()
.make_span_with(trace::DefaultMakeSpan::new().level(Level::INFO))
.on_response(trace::DefaultOnResponse::new().level(Level::INFO)),
),
)
}
}
fn fmt_trace() -> Result<(), String> {
tracing_subscriber::fmt()
.with_target(false)
.compact()
.try_init()
.map_err(|error| error.to_string())
}
#[cfg(all(test, feature = "axum"))] #[cfg(all(test, feature = "axum"))]
mod tests { mod tests {
use axum::Router; use axum::Router;
use super::*;
#[cfg(feature = "tokio")]
mod tokio_tests {
use std::time::Duration;
use tokio::time::sleep;
use super::*;
#[tokio::test]
async fn test_app_builder_serve() {
let handler = tokio::spawn(async {
AppBuilder::new().serve().await.unwrap();
});
sleep(Duration::from_secs(1)).await;
handler.abort();
}
#[tokio::test]
async fn test_app_builder_all() {
let handler = tokio::spawn(async {
AppBuilder::new()
.socket((Ipv4Addr::LOCALHOST, 8080))
.routes(&[Router::new()])
.fallback(|| async { "Fallback" })
.cors(CorsLayer::new())
.normalize_path(true)
.tracing(TraceLayer::new_for_http())
.serve()
.await
.unwrap();
});
sleep(Duration::from_secs(1)).await;
handler.abort();
}
}
#[test] #[test]
fn test_create_app_router_only() { fn test_create_app_router_only() {
let _app: Router<()> = create_app!(Router::new()); let _app: Router<()> = create_app!(Router::new());