diff options
Diffstat
| -rw-r--r-- | Cargo.lock | 320 | +320 −0 |
| -rw-r--r-- | Cargo.toml | 6 | +6 −0 |
| -rw-r--r-- | src/controller_web.rs | 33 | +33 −0 |
| -rw-r--r-- | src/controller_zig.rs | 93 | +93 −0 |
| -rw-r--r-- | src/main.rs | 43 | +15 −28 |
| -rw-r--r-- | src/service_config.rs | 23 | +23 −0 |
| -rw-r--r-- | src/service_storage.rs | 47 | +47 −0 |
| -rw-r--r-- | src/service_upstream.rs | 67 | +67 −0 |
8 files changed, 604 insertions, 28 deletions
diff --git a/Cargo.lock b/Cargo.lock index 5ad4e6b..377a028 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,6 +3,15 @@ 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" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -86,12 +95,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" [[package]] +name = "cc" +version = "1.2.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] name = "encoding_rs" version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -117,12 +152,39 @@ dependencies = [ ] [[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "find-msvc-tools" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" + +[[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] name = "form_urlencoded" version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -171,6 +233,18 @@ dependencies = [ ] [[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] name = "h2" version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -260,6 +334,23 @@ dependencies = [ "pin-utils", "smallvec", "tokio", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", ] [[package]] @@ -269,13 +360,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" dependencies = [ "bytes", + "futures-channel", "futures-core", + "futures-util", "http", "http-body", "hyper", + "libc", "pin-project-lite", + "socket2", "tokio", "tower-service", + "tracing", ] [[package]] @@ -301,6 +397,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] name = "lock_api" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -371,12 +473,73 @@ dependencies = [ ] [[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] +name = "openssl" +version = "0.10.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] name = "parking_lot" version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -418,6 +581,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] name = "proc-macro2" version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -436,6 +605,12 @@ dependencies = [ ] [[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] name = "redox_syscall" version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -445,18 +620,92 @@ dependencies = [ ] [[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "rustix" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] name = "ryu" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" [[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] name = "serde" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -522,6 +771,12 @@ dependencies = [ ] [[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] name = "signal-hook-registry" version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -577,6 +832,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" [[package]] +name = "tempfile" +version = "3.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" +dependencies = [ + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] name = "tokio" version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -605,6 +873,16 @@ dependencies = [ ] [[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] name = "tokio-util" version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -666,24 +944,54 @@ dependencies = [ ] [[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] name = "unicode-ident" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -773,6 +1081,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] name = "zmij" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -783,6 +1097,12 @@ name = "zorian" version = "0.1.0" dependencies = [ "axum", + "bytes", + "http-body-util", + "hyper", + "hyper-tls", + "hyper-util", "minijinja", + "regex", "tokio", ] diff --git a/Cargo.toml b/Cargo.toml index c9b2b98..d46c974 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,5 +10,11 @@ repository = "https://github.com/mrdimidium/Zorian" [dependencies] axum = { version = "0.8.8", features = ["http2", "macros", "multipart"] } +bytes = "1.11.0" +http-body-util = "0.1.3" +hyper = "1.8.1" +hyper-tls = "0.6.0" +hyper-util = { version = "0.1.19", features = ["client", "http1", "http2", "tokio"] } minijinja = "2.14.0" +regex = "1.12.2" tokio = { version = "1.49.0", features = ["full"] } diff --git a/src/controller_web.rs b/src/controller_web.rs new file mode 100644 --- /dev/null +++ b/src/controller_web.rs @@ -0,0 +1,33 @@ +use axum::{Router, extract, http, response, routing}; +use std::sync::Arc; + +/// Handles html pages rendering and static files +pub struct WebController { + jinja: minijinja::Environment<'static>, +} + +impl WebController { + pub fn new() -> Self { + let mut jinja = minijinja::Environment::new(); + jinja + .add_template("index", include_str!("./index.html")) + .unwrap(); + + Self { jinja } + } + + pub fn router(self: Arc<Self>) -> Router { + Router::new() + .route("/", routing::get(Self::index)) + .with_state(self) + } + + async fn index( + extract::State(controller): extract::State<Arc<Self>>, + ) -> Result<response::Html<String>, http::StatusCode> { + let template = controller.jinja.get_template("index").unwrap(); + let rendered = template.render(minijinja::context! {}).unwrap(); + + Ok(response::Html(rendered)) + } +} diff --git a/src/controller_zig.rs b/src/controller_zig.rs new file mode 100644 --- /dev/null +++ b/src/controller_zig.rs @@ -0,0 +1,93 @@ +use axum::{Router, body, extract, http, response, routing}; +use std::sync::{Arc, OnceLock}; + +use crate::service_storage; +use crate::service_upstream; + +pub struct ZigController { + storage: Arc<service_storage::StorageService>, + upstream: Arc<service_upstream::UpstreamService>, +} + +impl ZigController { + pub fn new( + storage: Arc<service_storage::StorageService>, + upstream: Arc<service_upstream::UpstreamService>, + ) -> Self { + Self { storage, upstream } + } + + pub fn router(self: Arc<Self>) -> Router { + Router::new() + .route("/zig/{filename}", routing::get(Self::download)) + .with_state(self) + } + + async fn download( + extract::State(controller): extract::State<Arc<Self>>, + extract::Path(filename): extract::Path<String>, + ) -> Result<response::Response, http::StatusCode> { + let version = Self::parse_version(&filename).ok_or(http::StatusCode::NOT_FOUND)?; + let url = Self::build_upstream_url(&filename, &version); + + match controller.storage.get(&filename).await { + Ok(Some(entry)) => { + return Ok(Self::build_response(http::StatusCode::OK, entry)); + } + Ok(None) => {} + Err(_) => { + return Err(http::StatusCode::INTERNAL_SERVER_ERROR); + } + } + + let entry = controller + .upstream + .fetch(service_upstream::DownloadRequest { url }) + .await?; + let cache_entry = service_storage::File { + bytes: entry.bytes.clone(), + }; + + match controller.storage.put(&filename, &cache_entry).await { + Ok(()) => {} + Err(_) => { + return Err(http::StatusCode::OK); + } + } + + Ok(Self::build_response(http::StatusCode::OK, cache_entry)) + } + + fn parse_version(filename: &str) -> Option<String> { + let re = Self::filename_regex(); + re.captures(filename) + .and_then(|captures| captures.get(1)) + .map(|match_| match_.as_str().to_string()) + } + + fn filename_regex() -> &'static regex::Regex { + static REGEX: OnceLock<regex::Regex> = OnceLock::new(); + REGEX.get_or_init(|| { + regex::Regex::new( + r"^zig(?:|-bootstrap|-[a-zA-Z0-9_]+-[a-zA-Z0-9_]+)-(\d+\.\d+\.\d+(?:-dev\.\d+\+[0-9a-f]+)?)\.(?:tar\.xz|zip)(?:\.minisig)?$", + ) + .unwrap() + }) + } + + fn build_upstream_url(filename: &str, version: &str) -> String { + if version.contains("-dev.") { + format!("https://ziglang.org/builds/{filename}") + } else { + format!("https://ziglang.org/download/{version}/{filename}") + } + } + + fn build_response(status: http::StatusCode, entry: service_storage::File) -> response::Response { + response::Response::builder() + .status(status) + .header(http::header::CONTENT_TYPE, "application/octet-stream") + .body(body::Body::from(entry.bytes)) + .unwrap() + } +} diff --git a/src/main.rs b/src/main.rs index bcfc67b..c7870b5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,40 +1,27 @@ -use axum::{Router, extract::State, http, response, routing}; -use std::sync; +use axum::Router; +use std::sync::Arc; -struct GlobalState { - jinja: minijinja::Environment<'static>, -} +mod controller_web; +mod controller_zig; +mod service_config; +mod service_storage; +mod service_upstream; #[tokio::main] async fn main() { - // init template engine and add templates - let mut jinja = minijinja::Environment::new(); - jinja - .add_template("index", include_str!("./index.html")) - .unwrap(); + let config = Arc::new(service_config::ConfigService::new()); + let storage = Arc::new(service_storage::StorageService::new(config.clone())); + let upstream = Arc::new(service_upstream::UpstreamService::new()); - let app_state = sync::Arc::new(GlobalState { jinja }); + let web_controller = Arc::new(controller_web::WebController::new()); + let zig_controller = Arc::new(controller_zig::ZigController::new(storage, upstream)); let app = Router::new() - .route("/", routing::get(handler_index)) - .with_state(app_state); + .merge(web_controller.router()) + .merge(zig_controller.router()); - let listener = tokio::net::TcpListener::bind("127.0.0.1:3000") - .await - .unwrap(); + let listener = tokio::net::TcpListener::bind(config.addr()).await.unwrap(); println!("listening on {}", listener.local_addr().unwrap()); axum::serve(listener, app).await.unwrap(); } - -async fn handler_index( - State(state): State<sync::Arc<GlobalState>>, -) -> Result<response::Html<String>, http::StatusCode> { - let template = state.jinja.get_template("index").unwrap(); - - let rendered = template - .render(minijinja::context! {}) - .unwrap(); - - Ok(response::Html(rendered)) -} diff --git a/src/service_config.rs b/src/service_config.rs new file mode 100644 --- /dev/null +++ b/src/service_config.rs @@ -0,0 +1,23 @@ +use std::path::{Path, PathBuf}; + +pub struct ConfigService { + addr: String, + dirname: PathBuf, +} + +impl ConfigService { + pub fn new() -> Self { + Self { + addr: "0.0.0.0:3000".to_string(), + dirname: PathBuf::from("./zorian-storage"), + } + } + + pub fn addr(&self) -> &str { + self.addr.as_str() + } + + pub fn dirname(&self) -> &Path { + self.dirname.as_path() + } +} diff --git a/src/service_storage.rs b/src/service_storage.rs new file mode 100644 --- /dev/null +++ b/src/service_storage.rs @@ -0,0 +1,47 @@ +use std::{io, path, sync}; +use tokio::fs; + +use super::service_config::ConfigService; + +#[derive(Clone)] +pub struct File { + pub bytes: bytes::Bytes, +} + +pub struct StorageService { + config: sync::Arc<ConfigService>, +} + +impl StorageService { + pub fn new(config: sync::Arc<ConfigService>) -> Self { + Self { config } + } + + pub async fn get(&self, filename: &str) -> Result<Option<File>, io::Error> { + let data_path = self.data_path(filename)?; + let bytes = match fs::read(&data_path).await { + Ok(bytes) => bytes, + Err(err) => { + return if err.kind() == io::ErrorKind::NotFound { + Ok(None) + } else { + Err(err) + }; + } + }; + + Ok(Some(File { + bytes: bytes.into(), + })) + } + + pub async fn put(&self, filename: &str, entry: &File) -> Result<(), io::Error> { + let data_path = self.data_path(filename)?; + fs::write(&data_path, &entry.bytes).await?; + Ok(()) + } + + fn data_path(&self, filename: &str) -> Result<path::PathBuf, io::Error> { + Ok(self.config.dirname().join(filename)) + } +} diff --git a/src/service_upstream.rs b/src/service_upstream.rs new file mode 100644 --- /dev/null +++ b/src/service_upstream.rs @@ -0,0 +1,67 @@ +use bytes::Bytes; +use http_body_util::{BodyExt, Empty}; +use hyper::{Request, http}; +use hyper_tls::HttpsConnector; +use hyper_util::client::legacy::{Client, connect::HttpConnector}; +use hyper_util::rt::TokioExecutor; + +#[derive(Clone)] +pub struct DownloadRequest { + pub url: String, +} + +#[derive(Clone)] +pub struct File { + pub bytes: Bytes, + pub content_type: Option<String>, +} + +pub struct UpstreamService { + client: Client<HttpsConnector<HttpConnector>, Empty<Bytes>>, +} + +impl UpstreamService { + pub fn new() -> Self { + let https = HttpsConnector::new(); + let client = Client::builder(TokioExecutor::new()).build(https); + Self { client } + } + + pub async fn fetch(&self, request: DownloadRequest) -> Result<File, http::StatusCode> { + let request = Request::builder() + .method(http::Method::GET) + .uri(&request.url) + .header(http::header::USER_AGENT, "zorian/0.1") + .body(Empty::<Bytes>::new()) + .unwrap(); + + let response = self + .client + .request(request) + .await + .map_err(|_| http::StatusCode::GATEWAY_TIMEOUT)?; + + let (parts, body) = response.into_parts(); + let status = parts.status; + if !status.is_success() { + return Err(status); + } + + let content_type = parts + .headers + .get(http::header::CONTENT_TYPE) + .and_then(|value| value.to_str().ok()) + .map(|value| value.to_string()); + + let bytes = body + .collect() + .await + .map_err(|_| http::StatusCode::GATEWAY_TIMEOUT)? + .to_bytes(); + + Ok(File { + bytes, + content_type, + }) + } +} |
