diff --git a/Cargo.lock b/Cargo.lock index aa486a1..ee0e1c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -632,6 +641,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.183" @@ -656,6 +671,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + [[package]] name = "matchit" version = "0.8.4" @@ -695,6 +719,15 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -713,10 +746,13 @@ version = "0.1.0" dependencies = [ "axum", "http-body-util", + "regex", "reqwest", "thiserror 2.0.18", "tokio", "tower-http", + "tracing", + "tracing-subscriber", ] [[package]] @@ -864,6 +900,35 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + [[package]] name = "reqwest" version = "0.13.2" @@ -1116,6 +1181,15 @@ dependencies = [ "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]] name = "shlex" version = "1.3.0" @@ -1258,6 +1332,15 @@ dependencies = [ "syn", ] +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + [[package]] name = "tinystr" version = "0.8.2" @@ -1397,9 +1480,21 @@ checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tracing-core" version = "0.1.36" @@ -1407,6 +1502,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "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.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", ] [[package]] @@ -1451,6 +1576,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "walkdir" version = "2.5.0" diff --git a/Cargo.toml b/Cargo.toml index 4ed8916..27911b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ edition = "2024" [dependencies] axum = "0.8.8" http-body-util = "0.1.3" +regex = "1.12.3" reqwest = "0.13.2" thiserror = "2.0.18" tokio = { version = "1.50.0", features = [ @@ -17,3 +18,5 @@ tokio = { version = "1.50.0", features = [ "signal", ] } tower-http = { version = "0.6.8", features = ["fs", "trace", "cors"] } +tracing = "0.1.44" +tracing-subscriber = { version = "0.3.23", features = ["env-filter"] } diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..9582727 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,66 @@ +# syntax=docker/dockerfile:1 + +# Comments are provided throughout this file to help you get started. +# If you need more help, visit the Dockerfile reference guide at +# https://docs.docker.com/reference/dockerfile/ + +################################################################################ +# Create a stage for building the application. + +ARG RUST_VERSION=1.93.1 +ARG APP_NAME=osm-proxy +FROM rust:${RUST_VERSION}-slim-bullseye AS build +ARG APP_NAME +WORKDIR /app + +# Build the application. +# Leverage a cache mount to /usr/local/cargo/registry/ +# for downloaded dependencies and a cache mount to /app/target/ for +# compiled dependencies which will speed up subsequent builds. +# Leverage a bind mount to the src directory to avoid having to copy the +# source code into the container. Once built, copy the executable to an +# output directory before the cache mounted /app/target is unmounted. +RUN --mount=type=bind,source=src,target=src \ + --mount=type=bind,source=Cargo.toml,target=Cargo.toml \ + --mount=type=bind,source=Cargo.lock,target=Cargo.lock \ + --mount=type=cache,target=/app/target/ \ + --mount=type=cache,target=/usr/local/cargo/registry/ \ + < Result<(), Box> { + tracing_subscriber::registry() + .with( + tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| { + format!("{}=debug,tower_http=debug", env!("CARGO_CRATE_NAME")).into() + }), + ) + .with(tracing_subscriber::fmt::layer()) + .init(); + let app = Router::new() .route("/{*path}", get(get_file)) .layer(CorsLayer::permissive()); - let listener = tokio::net::TcpListener::bind("127.0.0.1:3000") - .await - .unwrap(); + let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); + tracing::debug!("listening on {}", listener.local_addr().unwrap()); axum::serve(listener, app.into_make_service()).await?; Ok(()) } +static PATH_RE: LazyLock = + LazyLock::new(|| Regex::new(r"^(\d+)/(\d+)/(\d+)\.png$").unwrap()); async fn get_file(Path(path): Path) -> Result { - let local_path = PathBuf::from("static").join(&path); + if !PATH_RE.is_match(&path) { + tracing::warn!("Disallowed {path}"); + return Err(StatusCode::NOT_FOUND); + } + + let env_path = std::env::var("OSM_PATH").unwrap_or("static".into()); + let local_path = PathBuf::from(env_path).join(&path); // Check if file exists if fs::metadata(&local_path).await.is_err() { + tracing::info!("Downloading {path}"); // Build remote URL let remote_url = format!("https://osm.rrze.fau.de/osmhd/{}", path); // Download file - let response = reqwest::get(&remote_url) - .await - .map_err(|_| StatusCode::BAD_GATEWAY)?; + let response = reqwest::get(&remote_url).await.map_err(|e| { + tracing::error!("Unable to get remote url {}: {:?}", remote_url, e); + StatusCode::BAD_GATEWAY + })?; if !response.status().is_success() { + tracing::error!("File not found on remote url {}", remote_url); return Err(StatusCode::NOT_FOUND); } - let bytes = response - .bytes() - .await - .map_err(|_| StatusCode::BAD_GATEWAY)?; + let bytes = response.bytes().await.map_err(|e| { + tracing::error!("Unable to load bytes from response {}: {:?}", remote_url, e); + StatusCode::BAD_GATEWAY + })?; // Ensure directory exists if let Some(parent) = local_path.parent() { - fs::create_dir_all(parent) - .await - .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + fs::create_dir_all(parent).await.map_err(|_| { + tracing::error!("Unable to create directories {:?}", parent); + StatusCode::INTERNAL_SERVER_ERROR + })?; } // Save file - fs::write(&local_path, &bytes) - .await - .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + fs::write(&local_path, &bytes).await.map_err(|_| { + tracing::error!("Unable to write file {:?}", local_path); + StatusCode::INTERNAL_SERVER_ERROR + })?; } + tracing::info!("Serving {path}"); + // Serve file - let bytes = fs::read(&local_path) - .await - .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + let bytes = fs::read(&local_path).await.map_err(|_| { + tracing::error!("Unable to read file {:?}", local_path); + StatusCode::INTERNAL_SERVER_ERROR + })?; Ok(([(axum::http::header::CONTENT_TYPE, "image/png")], bytes)) }