From cd69433a2f2fe2f79034e638159cb102fba2c915 Mon Sep 17 00:00:00 2001 From: Nicolas Kamzol Date: Wed, 22 Oct 2025 12:38:29 +0200 Subject: [PATCH 1/4] Make the base path configurable - Adds the `url` dependency - New config `server.http.base-path` - Defaults to "/" - Change log entry for server start - Follow OpenTelemetry Semantic Conventions - Replace wildcard addresses (e.g. `0.0.0.0`) with `127.0.0.1` to quickly visit the URL in a browser --- Cargo.lock | 15 ++++++++------- Cargo.toml | 1 + src/api/aets.rs | 2 +- src/api/mod.rs | 12 +++++++++--- src/config/defaults.yaml | 1 + src/config/mod.rs | 22 ++++++++++++++++++++++ src/main.rs | 9 +++++++-- 7 files changed, 49 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1b7b822..6e9508c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -608,7 +608,7 @@ dependencies = [ "hyper-util", "pin-project-lite", "rustls 0.21.12", - "rustls 0.23.33", + "rustls 0.23.34", "rustls-native-certs 0.8.2", "rustls-pki-types", "tokio", @@ -1501,6 +1501,7 @@ dependencies = [ "tower-http", "tracing", "tracing-subscriber", + "url", "uuid", ] @@ -2274,7 +2275,7 @@ dependencies = [ "http 1.3.1", "hyper 1.7.0", "hyper-util", - "rustls 0.23.33", + "rustls 0.23.34", "rustls-native-certs 0.8.2", "rustls-pki-types", "tokio", @@ -3807,9 +3808,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.33" +version = "0.23.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "751e04a496ca00bb97a5e043158d23d66b5aabf2e1d5aa2a0aaebb1aafe6f82c" +checksum = "6a9586e9ee2b4f8fab52a0048ca7334d7024eef48e2cb9407e3497bb7cab7fa7" dependencies = [ "aws-lc-rs", "once_cell", @@ -4633,7 +4634,7 @@ version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls 0.23.33", + "rustls 0.23.34", "tokio", ] @@ -4872,9 +4873,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" +checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06" [[package]] name = "unicode-segmentation" diff --git a/Cargo.toml b/Cargo.toml index c7f78d2..57331e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,7 @@ futures = "0.3.31" mime = "0.3.17" tower-http = { version = "0.6.6", features = ["trace", "cors", "timeout"] } tower = { version = "0.5.2", features = ["limit"] } +url = "2.5.7" async-trait = "0.1.89" async-stream = "0.3.6" uuid = { version = "1.18.1", features = ["v4"] } diff --git a/src/api/aets.rs b/src/api/aets.rs index 7caa0a4..f57bc4c 100644 --- a/src/api/aets.rs +++ b/src/api/aets.rs @@ -5,7 +5,7 @@ use axum::response::IntoResponse; use axum::routing::get; use axum::{Json, Router}; -pub fn api() -> Router { +pub fn routes() -> Router { Router::new() .route("/aets", get(all_aets)) .route("/aets/{aet}", get(aet_health)) diff --git a/src/api/mod.rs b/src/api/mod.rs index 9e3d80e..6266595 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -6,12 +6,18 @@ pub mod qido; pub mod stow; pub mod wado; -pub fn routes() -> Router { - Router::new().merge(aets::api()).nest( +pub fn routes(base_path: &str) -> Router { + let router = Router::new().merge(aets::routes()).nest( "/aets/{aet}", Router::new() .merge(qido::routes()) .merge(wado::routes()) .merge(stow::routes()), - ) + ); + + // axum no longer supports nesting at the root + match base_path { + "/" | "" => router, + base_path => Router::new().nest(base_path, router), + } } diff --git a/src/config/defaults.yaml b/src/config/defaults.yaml index f210a46..99af723 100644 --- a/src/config/defaults.yaml +++ b/src/config/defaults.yaml @@ -9,6 +9,7 @@ server: max-upload-size: 50000000 request-timeout: 60000 graceful-shutdown: true + base-path: / dimse: - aet: DICOM-RST interface: 0.0.0.0 diff --git a/src/config/mod.rs b/src/config/mod.rs index 46e058e..d5154e4 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -232,6 +232,27 @@ pub struct HttpServerConfig { pub max_upload_size: usize, pub request_timeout: u64, pub graceful_shutdown: bool, + pub base_path: String, +} + +impl HttpServerConfig { + const WILDCARD_ADDRESSES: [&'static str; 3] = + ["0.0.0.0", "::", "0000:0000:0000:0000:0000:0000:0000:0000"]; + + pub fn base_url(&self) -> Result { + let origin = format!("http://{}:{}", self.interface, self.port); + let mut url = url::Url::parse(&origin)?; + + if url + .host() + .is_some_and(|host| Self::WILDCARD_ADDRESSES.contains(&host.to_string().as_str())) + { + url.set_host(Some("127.0.0.1"))?; + } + let url = url.join(&self.base_path)?; + + Ok(url) + } } impl Default for HttpServerConfig { @@ -242,6 +263,7 @@ impl Default for HttpServerConfig { graceful_shutdown: true, max_upload_size: 50_000_000, // 50 MB request_timeout: 60_000, // 1 min + base_path: String::from("/"), } } } diff --git a/src/main.rs b/src/main.rs index 385898d..74413bb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -129,7 +129,7 @@ async fn run(config: AppConfig) -> anyhow::Result<()> { }); } - let app = api::routes() + let app = api::routes(&config.server.http.base_path) .layer(CorsLayer::permissive()) .layer(axum::middleware::from_fn(add_common_headers)) .layer( @@ -152,7 +152,12 @@ async fn run(config: AppConfig) -> anyhow::Result<()> { let addr = SocketAddr::from((host, port)); let listener = TcpListener::bind(addr).await?; - info!("Started DICOMweb server on http://{addr}"); + info!( + server.address = addr.ip().to_string(), + server.port = addr.port(), + url.full = config.server.http.base_url()?.as_str(), + "Started DICOMweb server" + ); if config.server.http.graceful_shutdown { axum::serve(listener, app) .with_graceful_shutdown(shutdown_signal()) From a6b7e1609eeb499133652cbc284659f10d504652 Mon Sep 17 00:00:00 2001 From: Nicolas Kamzol Date: Wed, 22 Oct 2025 12:42:12 +0200 Subject: [PATCH 2/4] Display some basic server information at the root endpoint --- src/api/home.rs | 16 ++++++++++++++++ src/api/mod.rs | 18 +++++++++++------- 2 files changed, 27 insertions(+), 7 deletions(-) create mode 100644 src/api/home.rs diff --git a/src/api/home.rs b/src/api/home.rs new file mode 100644 index 0000000..876bd75 --- /dev/null +++ b/src/api/home.rs @@ -0,0 +1,16 @@ +use crate::AppState; +use axum::response::IntoResponse; +use axum::routing::get; +use axum::Router; + +pub fn routes() -> Router { + Router::new().route("/", get(index)) +} + +// TODO: Return HTML page for a quick user-friendly overview +async fn index() -> impl IntoResponse { + format!( + "This server is running DICOM-RST (v{})", + env!("CARGO_PKG_VERSION") + ) +} diff --git a/src/api/mod.rs b/src/api/mod.rs index 6266595..8690e27 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -2,18 +2,22 @@ use crate::AppState; use axum::Router; mod aets; +mod home; pub mod qido; pub mod stow; pub mod wado; pub fn routes(base_path: &str) -> Router { - let router = Router::new().merge(aets::routes()).nest( - "/aets/{aet}", - Router::new() - .merge(qido::routes()) - .merge(wado::routes()) - .merge(stow::routes()), - ); + let router = Router::new() + .merge(home::routes()) + .merge(aets::routes()) + .nest( + "/aets/{aet}", + Router::new() + .merge(qido::routes()) + .merge(wado::routes()) + .merge(stow::routes()), + ); // axum no longer supports nesting at the root match base_path { From 989c1e59f5e2d427364566503275af639f0fabc2 Mon Sep 17 00:00:00 2001 From: Nicolas Kamzol Date: Wed, 22 Oct 2025 12:54:32 +0200 Subject: [PATCH 3/4] Set override option: DICOM_RST_SERVER_HTTP_BASE_PATH --- src/config/mod.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/config/mod.rs b/src/config/mod.rs index d5154e4..2a59781 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -201,6 +201,10 @@ impl AppConfig { )) .add_source(File::with_name("config.yaml").required(false)) .add_source(Environment::with_prefix("DICOM_RST").separator("_")) + .set_override_option( + "server.http.base-path", + std::env::var("DICOM_RST_SERVER_HTTP_BASE_PATH").ok(), + )? .build()? .try_deserialize() } From 5ee1221ae4bbecab4e48b011ee0247194c1bb2e6 Mon Sep 17 00:00:00 2001 From: Nicolas Kamzol Date: Wed, 22 Oct 2025 12:59:41 +0200 Subject: [PATCH 4/4] Add `server.http.base-path` to documentation --- docs/topics/configuration.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/topics/configuration.md b/docs/topics/configuration.md index 0f1006a..fdb57d3 100644 --- a/docs/topics/configuration.md +++ b/docs/topics/configuration.md @@ -124,6 +124,7 @@ server: max-upload-size: 50000000 request-timeout: 60000 graceful-shutdown: true + base-path: / ``` @@ -145,6 +146,9 @@ server: Shutdowns will take no longer than the request timeout configured by server.http.request.timeout. If disabled, the process is stopped immediately, which will potentially lead to incomplete responses for outstanding requests. + + Sets the base path for all HTTP endpoints. + ## DIMSE Server Config