diff --git a/.idea/acrate.iml b/.idea/acrate.iml index 8ef4dcf..01a6e70 100644 --- a/.idea/acrate.iml +++ b/.idea/acrate.iml @@ -12,6 +12,7 @@ + diff --git a/Cargo.toml b/Cargo.toml index d528923..1e44dab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,3 @@ [workspace] resolver = "2" -members = ["acrate_database", "acrate_rd", "acrate_nodeinfo", "acrate_rdserver"] +members = ["acrate_database", "acrate_rd", "acrate_nodeinfo", "acrate_rdserver", "acrate_utils"] diff --git a/acrate_rdserver/Cargo.toml b/acrate_rdserver/Cargo.toml index ac749de..fe2421e 100644 --- a/acrate_rdserver/Cargo.toml +++ b/acrate_rdserver/Cargo.toml @@ -12,12 +12,12 @@ categories = ["web-programming"] [dependencies] acrate_database = { path = "../acrate_database", features = ["connect"] } acrate_rd = { path = "../acrate_rd" } +acrate_utils = { path = "../acrate_utils" } anyhow = "1.0.93" axum = { version = "0.7.7", features = ["macros"] } axum-extra = { version = "0.9.4", features = ["query"] } log = { version = "0.4.22", features = ["std", "max_level_trace", "release_max_level_debug"] } micronfig = "0.3.0" -minijinja = "2.5.0" pretty_env_logger = "0.5.0" quick-xml = { version = "0.37.0", features = ["serialize"] } serde = { version = "1.0.215", features = ["derive"] } diff --git a/acrate_rdserver/src/config.rs b/acrate_rdserver/src/config.rs index 535a5c4..786b401 100644 --- a/acrate_rdserver/src/config.rs +++ b/acrate_rdserver/src/config.rs @@ -1,3 +1,3 @@ micronfig::config!( - ACRATE_WEBFINGER_BIND_ADDRESS: String, + ACRATE_WEBFINGER_BIND_ADDRESS: String > std::net::SocketAddr, ); diff --git a/acrate_rdserver/src/main.rs b/acrate_rdserver/src/main.rs index 60dffed..f926ac2 100644 --- a/acrate_rdserver/src/main.rs +++ b/acrate_rdserver/src/main.rs @@ -1,42 +1,19 @@ -use std::sync::Arc; -use anyhow::Context; -use axum::Extension; +use axum::routing::get; +use acrate_utils::web_server; mod config; mod route; - #[tokio::main] -async fn main() -> anyhow::Result { - pretty_env_logger::init(); - log::debug!("Logging initialized!"); - - log::trace!("Creating Minijinja environment..."); - let mut mj = minijinja::Environment::<'static>::new(); - - log::trace!("Adding webfinger page to the Minijinja environment..."); - mj.add_template("rd.html.j2", include_str!("rd.html.j2")) - .expect("rd.html.j2 to be a valid Minijinja template"); - - log::trace!("Creating Axum router..."); - let app = axum::Router::new() - .route("/*path", axum::routing::get(route::webfinger_handler)) - .route("/.healthcheck", axum::routing::get(route::healthcheck_handler)) - .layer(Extension(Arc::new(mj))); - log::trace!("Axum router created successfully!"); - - log::trace!("Creating Tokio listener..."); - let bind_address = config::ACRATE_WEBFINGER_BIND_ADDRESS(); - let listener = tokio::net::TcpListener::bind(bind_address) - .await - .context("failed to bind listener to address")?; - log::trace!("Tokio listener bound to: {bind_address}"); - - log::info!("Starting server..."); - axum::serve(listener, app) - .await - .context("server exited with error")?; - - log::error!("Server exited with no error, panicking."); - panic!("server exited with no error"); +async fn main() { + web_server!( + on: *config::ACRATE_WEBFINGER_BIND_ADDRESS(), + templates: [ + "rd.html.j2" + ], + routes: { + "/*path" => get(route::webfinger_handler), + "/.healthcheck" => get(route::healthcheck_handler) + } + ); } diff --git a/acrate_rdserver/src/route.rs b/acrate_rdserver/src/route.rs index a601283..34fa201 100644 --- a/acrate_rdserver/src/route.rs +++ b/acrate_rdserver/src/route.rs @@ -1,5 +1,4 @@ use std::iter::IntoIterator; -use std::sync::Arc; use axum::Extension; use axum::extract::Path; use axum::http::{HeaderMap, Response, StatusCode}; @@ -11,6 +10,7 @@ use acrate_database::diesel::GroupedBy; use acrate_database::meta::{MetaAlias, MetaLink, MetaLinkProperty, MetaLinkTitle, MetaProperty, MetaSubject}; use acrate_rd::jrd::ResourceDescriptorLinkJRD; use acrate_rd::xrd::{ResourceDescriptorLinkXRD, ResourceDescriptorPropertyXRD, ResourceDescriptorTitleXRD}; +use acrate_utils::ext::{minijinja, ExtMj}; pub async fn healthcheck_handler() -> Result { log::debug!("Handling an healthcheck request!"); @@ -36,7 +36,7 @@ pub async fn webfinger_handler( Path(path): Path, Query(WebfingerQuery {resource, rel}): Query, headers: HeaderMap, - Extension(mj): Extension>>, + Extension(mj): ExtMj, ) -> Result, StatusCode> { log::debug!("Handling a WebFinger request!"); diff --git a/acrate_utils/Cargo.toml b/acrate_utils/Cargo.toml new file mode 100644 index 0000000..1497ca2 --- /dev/null +++ b/acrate_utils/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "acrate_utils" +version = "0.3.0" +authors = ["Stefano Pigozzi "] +edition = "2021" +description = "Utilities for the acrate project" +repository = "https://forge.steffo.eu/unimore/tirocinio-canali-steffo-acrate" +license = "EUPL-1.2" +keywords = [] +categories = [] + +[dependencies] +axum = { version = "0.7.7", features = ["macros"] } +axum-extra = { version = "0.9.4", features = ["query"] } +log = { version = "0.4.22", features = ["std", "max_level_trace", "release_max_level_debug"] } +pretty_env_logger = "0.5.0" +mediatype = { version = "0.19.18", features = ["serde"] } +minijinja = "2.5.0" +tokio = { version = "1.41.1", features = ["net"] } + +[lints.clippy] +tabs-in-doc-comments = "allow" diff --git a/acrate_utils/src/ext.rs b/acrate_utils/src/ext.rs new file mode 100644 index 0000000..99fef0c --- /dev/null +++ b/acrate_utils/src/ext.rs @@ -0,0 +1,6 @@ +use std::sync::Arc; +use axum::Extension; + +pub use minijinja; + +pub type ExtMj = Extension>>; diff --git a/acrate_utils/src/init.rs b/acrate_utils/src/init.rs new file mode 100644 index 0000000..e5c73d2 --- /dev/null +++ b/acrate_utils/src/init.rs @@ -0,0 +1,49 @@ +use std::net::SocketAddr; +use std::sync::Arc; +use axum::Extension; + +/// Initialize logging with [`pretty_env_logger::init`]. +pub fn init_logging() { + log::trace!("Initializing logging..."); + pretty_env_logger::init(); + log::trace!("Initialized logging!"); +} + +/// Initialize a [`tokio::net::TcpListener`] bound to the given [`SocketAddr`]. +/// +/// # Panics +/// +/// If unable to bind to the given [`SocketAddr`]. +/// +pub async fn init_listener(bind: SocketAddr) -> tokio::net::TcpListener { + log::trace!("Creating Tokio listener bound to: {bind:#?}"); + let listener = tokio::net::TcpListener::bind(bind) + .await + .unwrap_or_else(|_| panic!("Failed to bind to: {bind:#?}")); + log::trace!("Created Tokio listener: {listener:#?}"); + + listener +} + +/// Initialize a static [`minijinja::Environment`]. +pub fn init_minijinja() -> minijinja::Environment<'static> { + log::trace!("Creating Minijinja environment..."); + let mj = minijinja::Environment::<'static>::new(); + log::trace!("Created Minijinja environment: {mj:#?}"); + + mj +} + +/// Initialize an [`axum::Router`] with the given [`minijinja::Environment`] available. +pub fn init_router(mj: minijinja::Environment<'static>) -> axum::Router { + log::trace!("Creating Arc for the minijinja Environment..."); + let mj = Arc::new(mj); + log::trace!("Created Arc for the minijinja Environment: {mj:#?}"); + + log::trace!("Creating Axum router..."); + let router = axum::Router::new() + .layer(Extension(mj)); + log::trace!("Created Axum router: {router:#?}"); + + router +} diff --git a/acrate_utils/src/lib.rs b/acrate_utils/src/lib.rs new file mode 100644 index 0000000..1187539 --- /dev/null +++ b/acrate_utils/src/lib.rs @@ -0,0 +1,50 @@ +pub mod ext; +pub mod init; +pub mod run; + + +/// Add the template file at the given path to the given [`minijinja::Environment`]. +#[macro_export] +macro_rules! add_minijinja_template { + ($mj:ident, $path:literal) => { + log::trace!("Adding template to minijinja Environment: {:?} ← {:#?}", $mj, $path); + $mj.add_template($path, include_str!($path)) + .expect(concat!("Invalid Minijinja template: ", $path)); + log::trace!("Added template to minijinja Environment: {:?} ← {:#?}", $mj, $path); + }; +} + +/// Add the given route to the [`axum::Router`]. +#[macro_export] +macro_rules! add_axum_route { + ($router:ident, $path:literal, $route:expr) => { + log::trace!("Adding route to axum Router: {:?} ← {:#?}", $router, $path); + let $router = $router.route($path, $route); + log::trace!("Added route to axum Router: {:?} ← {:#?}", $router, $path); + }; +} + +#[macro_export] +macro_rules! web_server { + ( + on: $socket_addr:expr, + templates: [ + $( $template_path:literal ),+ + ], + routes: { + $( $path:literal => $route:expr ),+ + } + ) => { + $crate::init::init_logging(); + let listener = $crate::init::init_listener($socket_addr).await; + let mut mj = $crate::init::init_minijinja(); + $( + $crate::add_minijinja_template!(mj, $template_path); + )+ + let router = $crate::init::init_router(mj); + $( + $crate::add_axum_route!(router, $path, $route); + )+ + $crate::run::run_server(listener, router).await + }; +} diff --git a/acrate_utils/src/run.rs b/acrate_utils/src/run.rs new file mode 100644 index 0000000..3185be8 --- /dev/null +++ b/acrate_utils/src/run.rs @@ -0,0 +1,25 @@ +/// Run a web server with [`axum::serve`], using the given [`tokio::net::TcpListener`] and [`axum::Router`]. +/// +/// # Exits +/// +/// Once the server is terminated, the process will exit with either: +/// +/// - `0`, if [`axum::serve`] returned [`Ok`]; +/// - `1`, if [`axum::serve`] returned [`Err`]. +/// +pub async fn run_server(listener: tokio::net::TcpListener, application: axum::Router) -> std::convert::Infallible { + log::trace!("Serving application: {listener:#?} → {application:#?}"); + let result = axum::serve(listener, application) + .await; + + match result { + Ok(()) => { + log::error!("Server exited gracefully."); + std::process::exit(0); + } + Err(e) => { + log::error!("Server exited with an error: {e:#?}"); + std::process::exit(1); + } + } +}