From 0d39be95cef9ef9273a761f29c336fd79ae5f665 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Wed, 6 Nov 2024 18:47:27 -0500 Subject: [PATCH] Move from tinyhttp to rouille Move away from TinyHTTP to a framework that provides: 1. A way to server static files via one function. 2. A better concurrency solution that isn't frought with peril for our usage. --- Cargo.lock | 188 ++++++++++++++++++++++++++++++- Cargo.toml | 2 +- crates/weaver_common/Cargo.toml | 2 +- crates/weaver_common/src/test.rs | 102 ++++------------- 4 files changed, 211 insertions(+), 83 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5871b2b5..b9bdece3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,6 +23,12 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "adler32" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" + [[package]] name = "aes" version = "0.8.4" @@ -57,6 +63,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + [[package]] name = "allocator-api2" version = "0.2.18" @@ -223,6 +244,12 @@ dependencies = [ "backtrace", ] +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "base64" version = "0.21.7" @@ -268,6 +295,27 @@ dependencies = [ "generic-array", ] +[[package]] +name = "brotli" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d640d25bc63c50fb1f0b545ffd80207d2e10a4c965530809b40ba3386825c391" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + [[package]] name = "bstr" version = "1.10.0" @@ -279,6 +327,16 @@ dependencies = [ "serde", ] +[[package]] +name = "buf_redux" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f" +dependencies = [ + "memchr", + "safemem", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -649,6 +707,16 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" +[[package]] +name = "deflate" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c86f7e25f518f4b81808a2cf1c50996a61f5c2eb394b2393bd87f2a4780a432f" +dependencies = [ + "adler32", + "gzip-header", +] + [[package]] name = "deflate64" version = "0.1.9" @@ -1723,6 +1791,15 @@ dependencies = [ "serde", ] +[[package]] +name = "gzip-header" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95cc527b92e6029a62960ad99aa8a6660faa4555fe5f731aab13aa6a921795a2" +dependencies = [ + "crc32fast", +] + [[package]] name = "h2" version = "0.4.6" @@ -2378,6 +2455,16 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "minijinja" version = "2.4.0" @@ -2439,6 +2526,24 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "multipart" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182" +dependencies = [ + "buf_redux", + "httparse", + "log", + "mime", + "mime_guess", + "quick-error", + "rand 0.8.5", + "safemem", + "tempfile", + "twoway", +] + [[package]] name = "nom" version = "7.1.3" @@ -2534,6 +2639,25 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + [[package]] name = "object" version = "0.36.3" @@ -2861,6 +2985,12 @@ dependencies = [ "parking_lot", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quinn" version = "0.11.3" @@ -3180,6 +3310,30 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rouille" +version = "3.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3716fbf57fc1084d7a706adf4e445298d123e4a44294c4e8213caf1b85fcc921" +dependencies = [ + "base64 0.13.1", + "brotli", + "chrono", + "deflate", + "filetime", + "multipart", + "percent-encoding", + "rand 0.8.5", + "serde", + "serde_derive", + "serde_json", + "sha1_smol", + "threadpool", + "time", + "tiny_http", + "url", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -3259,6 +3413,12 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "safemem" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" + [[package]] name = "same-file" version = "1.0.6" @@ -3730,6 +3890,15 @@ dependencies = [ "syn", ] +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + [[package]] name = "time" version = "0.3.36" @@ -3738,7 +3907,9 @@ checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", + "libc", "num-conv", + "num_threads", "powerfmt", "serde", "time-core", @@ -3924,6 +4095,15 @@ dependencies = [ "unicode-width 0.2.0", ] +[[package]] +name = "twoway" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1" +dependencies = [ + "memchr", +] + [[package]] name = "typenum" version = "1.17.0" @@ -3939,6 +4119,12 @@ dependencies = [ "arrayvec", ] +[[package]] +name = "unicase" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" + [[package]] name = "unicode-bidi" version = "0.3.15" @@ -4300,10 +4486,10 @@ version = "0.10.0" dependencies = [ "miette", "paris", + "rouille", "serde", "serde_json", "thiserror", - "tiny_http", "ureq", ] diff --git a/Cargo.toml b/Cargo.toml index 7dabc262..a8c12e61 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,7 +56,7 @@ schemars = "0.8.21" dirs = "5.0.1" once_cell = "1.20.2" opentelemetry = { version = "0.23.0", features = ["trace", "metrics", "logs", "otel_unstable"] } -tiny_http = "0.12.0" +rouille = "3.6.2" # Features definition ========================================================= [features] diff --git a/crates/weaver_common/Cargo.toml b/crates/weaver_common/Cargo.toml index 63d26d14..1ce281f5 100644 --- a/crates/weaver_common/Cargo.toml +++ b/crates/weaver_common/Cargo.toml @@ -17,7 +17,7 @@ serde.workspace = true serde_json.workspace = true miette.workspace = true thiserror.workspace = true -tiny_http.workspace = true +rouille.workspace = true [dev-dependencies] ureq.workspace = true diff --git a/crates/weaver_common/src/test.rs b/crates/weaver_common/src/test.rs index 8550cdf5..e6a03b88 100644 --- a/crates/weaver_common/src/test.rs +++ b/crates/weaver_common/src/test.rs @@ -3,13 +3,10 @@ //! HTTP server for testing purposes. use paris::error; -use std::ffi::OsStr; -use std::fs::File; use std::path::PathBuf; -use std::str::FromStr; -use std::sync::Arc; -use std::{collections::HashMap, thread::JoinHandle}; -use tiny_http::{Header, Response, Server, StatusCode}; + +use rouille::{match_assets, Server}; +use std::sync::mpsc::Sender; /// An error that can occur while starting the HTTP server. #[derive(thiserror::Error, Debug, Clone)] @@ -20,23 +17,15 @@ pub struct HttpServerError { /// A struct that serves static files from a directory. pub struct ServeStaticFiles { - server: Arc, + kill_switch: Sender<()>, port: u16, - request_handler: JoinHandle<()>, } impl Drop for ServeStaticFiles { /// Stops the HTTP server. fn drop(&mut self) { - // Test to see if we can force tiny_http to kill our thread, dropping the Arc - // before we continue to try to ensure `server` is dropped, cleaning - // open threads. - let mut attempts = 0; - while !self.request_handler.is_finished() && attempts < 10 { - self.server.unblock(); - std::thread::yield_now(); - attempts += 1; - } + // If we fail to kill the server, ignore it. + let _ = self.kill_switch.send(()); } } @@ -44,69 +33,16 @@ impl ServeStaticFiles { /// Creates a new HTTP server that serves static files from a directory. /// Note: This server is only available for testing purposes. pub fn from(static_path: impl Into) -> Result { - let server = Server::http("127.0.0.1:0").map_err(|e| HttpServerError { - error: e.to_string(), - })?; - - let content_types: HashMap<&'static str, &'static str> = [ - ("yaml", "application/yaml"), - ("json", "application/json"), - ("zip", "application/zip"), - ("gz", "application/gzip"), - ] - .iter() - .cloned() - .collect(); - let static_path = static_path.into(); - let server = Arc::new(server); - let server_clone = server.clone(); - let port = server - .server_addr() - .to_ip() - .map(|ip| ip.port()) - .unwrap_or(0); - - let request_handler = std::thread::spawn(move || { - for request in server_clone.incoming_requests() { - let mut file_path = static_path.clone(); - if request.url().len() > 1 { - for chunk in request.url().trim_start_matches('/').split('/') { - file_path.push(chunk); - } - } - - if !file_path.exists() { - let status = StatusCode(404); - request - .respond(Response::empty(status)) - .expect("Failed to respond"); - } else if let Ok(file) = File::open(&file_path) { - let mut response = Response::from_file(file); - let content_type = file_path - .extension() - .and_then(OsStr::to_str) - .and_then(|ext| content_types.get(ext).copied()) - .unwrap_or("text/plain"); - response.add_header( - Header::from_str(&format!("Content-Type: {}", content_type)) - .expect("Failed to parse header"), - ); - request.respond(response).expect("Failed to respond"); - } else { - let status = StatusCode(500); - request - .respond(Response::empty(status)) - .expect("Failed to respond"); - } - } - }); - - Ok(Self { - server, - port, - request_handler, + let server = Server::new("127.0.0.1:0", move |request| { + match_assets(&request, &static_path) }) + .map_err(|e| HttpServerError { + error: e.to_string(), + })?; + let port = server.server_addr().port(); + let (_, kill_switch) = server.stoppable(); + Ok(Self { kill_switch, port }) } /// Returns the port of the server. @@ -137,14 +73,20 @@ mod tests { .call() .unwrap(); assert_eq!(content.status(), 200); - assert_eq!(content.header("Content-Type").unwrap(), "application/yaml"); + assert_eq!( + content.header("Content-Type").unwrap(), + "application/octet-stream" + ); assert_eq!(content.into_string().unwrap(), "file: A"); let content = ureq::get(&server.relative_path_to_url("file_b.yaml")) .call() .unwrap(); assert_eq!(content.status(), 200); - assert_eq!(content.header("Content-Type").unwrap(), "application/yaml"); + assert_eq!( + content.header("Content-Type").unwrap(), + "application/octet-stream" + ); assert_eq!(content.into_string().unwrap(), "file: B"); let result = ureq::get(&server.relative_path_to_url("unknown_file.yaml")).call();