utils: Create and use crate (#9)

Reviewed-on: #9
This commit is contained in:
Steffo 2024-12-18 15:03:13 +00:00 committed by Cross
commit da7ec46cbe
Signed by: forgejo
GPG key ID: 3277D7B12BD4777D
11 changed files with 171 additions and 41 deletions

View file

@ -12,6 +12,7 @@
<sourceFolder url="file://$MODULE_DIR$/acrate_mime/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/acrate_mime/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/acrate_rdserver/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/acrate_rdserver/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/acrate_database/tests" isTestSource="true" /> <sourceFolder url="file://$MODULE_DIR$/acrate_database/tests" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/acrate_utils/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" /> <excludeFolder url="file://$MODULE_DIR$/target" />
</content> </content>
<orderEntry type="inheritedJdk" /> <orderEntry type="inheritedJdk" />

View file

@ -1,3 +1,3 @@
[workspace] [workspace]
resolver = "2" resolver = "2"
members = ["acrate_database", "acrate_rd", "acrate_nodeinfo", "acrate_rdserver"] members = ["acrate_database", "acrate_rd", "acrate_nodeinfo", "acrate_rdserver", "acrate_utils"]

View file

@ -12,12 +12,12 @@ categories = ["web-programming"]
[dependencies] [dependencies]
acrate_database = { path = "../acrate_database", features = ["connect"] } acrate_database = { path = "../acrate_database", features = ["connect"] }
acrate_rd = { path = "../acrate_rd" } acrate_rd = { path = "../acrate_rd" }
acrate_utils = { path = "../acrate_utils" }
anyhow = "1.0.93" anyhow = "1.0.93"
axum = { version = "0.7.7", features = ["macros"] } axum = { version = "0.7.7", features = ["macros"] }
axum-extra = { version = "0.9.4", features = ["query"] } axum-extra = { version = "0.9.4", features = ["query"] }
log = { version = "0.4.22", features = ["std", "max_level_trace", "release_max_level_debug"] } log = { version = "0.4.22", features = ["std", "max_level_trace", "release_max_level_debug"] }
micronfig = "0.3.0" micronfig = "0.3.0"
minijinja = "2.5.0"
pretty_env_logger = "0.5.0" pretty_env_logger = "0.5.0"
quick-xml = { version = "0.37.0", features = ["serialize"] } quick-xml = { version = "0.37.0", features = ["serialize"] }
serde = { version = "1.0.215", features = ["derive"] } serde = { version = "1.0.215", features = ["derive"] }

View file

@ -1,3 +1,3 @@
micronfig::config!( micronfig::config!(
ACRATE_WEBFINGER_BIND_ADDRESS: String, ACRATE_WEBFINGER_BIND_ADDRESS: String > std::net::SocketAddr,
); );

View file

@ -1,42 +1,19 @@
use std::sync::Arc; use axum::routing::get;
use anyhow::Context; use acrate_utils::web_server;
use axum::Extension;
mod config; mod config;
mod route; mod route;
#[tokio::main] #[tokio::main]
async fn main() -> anyhow::Result<std::convert::Infallible> { async fn main() {
pretty_env_logger::init(); web_server!(
log::debug!("Logging initialized!"); on: *config::ACRATE_WEBFINGER_BIND_ADDRESS(),
templates: [
log::trace!("Creating Minijinja environment..."); "rd.html.j2"
let mut mj = minijinja::Environment::<'static>::new(); ],
routes: {
log::trace!("Adding webfinger page to the Minijinja environment..."); "/*path" => get(route::webfinger_handler),
mj.add_template("rd.html.j2", include_str!("rd.html.j2")) "/.healthcheck" => get(route::healthcheck_handler)
.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");
} }

View file

@ -1,5 +1,4 @@
use std::iter::IntoIterator; use std::iter::IntoIterator;
use std::sync::Arc;
use axum::Extension; use axum::Extension;
use axum::extract::Path; use axum::extract::Path;
use axum::http::{HeaderMap, Response, StatusCode}; 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_database::meta::{MetaAlias, MetaLink, MetaLinkProperty, MetaLinkTitle, MetaProperty, MetaSubject};
use acrate_rd::jrd::ResourceDescriptorLinkJRD; use acrate_rd::jrd::ResourceDescriptorLinkJRD;
use acrate_rd::xrd::{ResourceDescriptorLinkXRD, ResourceDescriptorPropertyXRD, ResourceDescriptorTitleXRD}; use acrate_rd::xrd::{ResourceDescriptorLinkXRD, ResourceDescriptorPropertyXRD, ResourceDescriptorTitleXRD};
use acrate_utils::ext::{minijinja, ExtMj};
pub async fn healthcheck_handler() -> Result<StatusCode, StatusCode> { pub async fn healthcheck_handler() -> Result<StatusCode, StatusCode> {
log::debug!("Handling an healthcheck request!"); log::debug!("Handling an healthcheck request!");
@ -36,7 +36,7 @@ pub async fn webfinger_handler(
Path(path): Path<String>, Path(path): Path<String>,
Query(WebfingerQuery {resource, rel}): Query<WebfingerQuery>, Query(WebfingerQuery {resource, rel}): Query<WebfingerQuery>,
headers: HeaderMap, headers: HeaderMap,
Extension(mj): Extension<Arc<minijinja::Environment<'static>>>, Extension(mj): ExtMj,
) -> Result<Response<String>, StatusCode> { ) -> Result<Response<String>, StatusCode> {
log::debug!("Handling a WebFinger request!"); log::debug!("Handling a WebFinger request!");

22
acrate_utils/Cargo.toml Normal file
View file

@ -0,0 +1,22 @@
[package]
name = "acrate_utils"
version = "0.3.0"
authors = ["Stefano Pigozzi <me@steffo.eu>"]
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"

6
acrate_utils/src/ext.rs Normal file
View file

@ -0,0 +1,6 @@
use std::sync::Arc;
use axum::Extension;
pub use minijinja;
pub type ExtMj = Extension<Arc<minijinja::Environment<'static>>>;

49
acrate_utils/src/init.rs Normal file
View file

@ -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
}

50
acrate_utils/src/lib.rs Normal file
View file

@ -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
};
}

25
acrate_utils/src/run.rs Normal file
View file

@ -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);
}
}
}