diff options
| author | Arnaud Bailly <arnaud.bailly@iohk.io> | 2024-10-04 17:17:10 +0200 |
|---|---|---|
| committer | Arnaud Bailly <arnaud.bailly@iohk.io> | 2024-10-04 17:17:10 +0200 |
| commit | c7ecec3f7e64bfbc8dc0231d538599716e1ccf88 (patch) | |
| tree | b1c9ba512bb5d6df532417bafa66b824f5248773 | |
| parent | 47533afd36990b486f1a5d66851f886510c04c20 (diff) | |
| download | lambda-nantes-c7ecec3f7e64bfbc8dc0231d538599716e1ccf88.tar.gz | |
Spawn thread to start sending requests to clients
| -rw-r--r-- | rust/Cargo.lock | 362 | ||||
| -rw-r--r-- | rust/Cargo.toml | 13 | ||||
| -rw-r--r-- | rust/src/web.rs | 167 |
3 files changed, 480 insertions, 62 deletions
diff --git a/rust/Cargo.lock b/rust/Cargo.lock index cd32246..90dc032 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -253,6 +253,125 @@ dependencies = [ ] [[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +dependencies = [ + "async-channel 2.3.1", + "async-executor", + "async-io", + "async-lock", + "blocking", + "futures-lite", + "once_cell", +] + +[[package]] +name = "async-io" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8" +dependencies = [ + "async-lock", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener 5.3.1", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-std" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c634475f29802fde2b8f0b505b1bd00dfe4df7d4a000f0b36f7671197d5c3615" +dependencies = [ + "async-channel 1.9.0", + "async-global-executor", + "async-io", + "async-lock", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] name = "atty" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -321,6 +440,19 @@ dependencies = [ ] [[package]] +name = "blocking" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +dependencies = [ + "async-channel 2.3.1", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] name = "brotli" version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -401,6 +533,15 @@ dependencies = [ ] [[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] name = "convert_case" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -442,6 +583,12 @@ dependencies = [ ] [[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] name = "crypto-common" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -522,6 +669,33 @@ dependencies = [ ] [[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "5.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +dependencies = [ + "event-listener 5.3.1", + "pin-project-lite", +] + +[[package]] name = "fastrand" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -553,12 +727,78 @@ dependencies = [ ] [[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] name = "futures-core" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-lite" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] name = "futures-sink" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -576,10 +816,16 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ + "futures-channel", "futures-core", + "futures-io", + "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", + "slab", ] [[package]] @@ -610,6 +856,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" [[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] name = "h2" version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -650,6 +908,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + +[[package]] name = "http" version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -752,6 +1016,15 @@ dependencies = [ ] [[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + +[[package]] name = "language-tags" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -813,6 +1086,9 @@ name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +dependencies = [ + "value-bag", +] [[package]] name = "memchr" @@ -880,6 +1156,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] name = "parking_lot" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -927,12 +1209,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] name = "pkg-config" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] +name = "polling" +version = "3.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.4.0", + "pin-project-lite", + "rustix", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1079,13 +1387,16 @@ name = "rust" version = "0.1.0" dependencies = [ "actix-web", + "async-std", "chrono", "env_logger", + "futures", "log", "proptest", "rand", "serde", "serde_json", + "uuid", ] [[package]] @@ -1415,6 +1726,35 @@ dependencies = [ ] [[package]] +name = "uuid" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +dependencies = [ + "getrandom", + "rand", + "serde", + "uuid-macro-internal", +] + +[[package]] +name = "uuid-macro-internal" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee1cd046f83ea2c4e920d6ee9f7c3537ef928d75dce5d84a87c2c5d6b3999a3a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "value-bag" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a84c137d37ab0142f0f2ddfe332651fdbf252e7b7dbb4e67b6c1f1b2e925101" + +[[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1462,6 +1802,18 @@ dependencies = [ ] [[package]] +name = "wasm-bindgen-futures" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] name = "wasm-bindgen-macro" version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1491,6 +1843,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" [[package]] +name = "web-sys" +version = "0.3.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 4deb7ec..8e75422 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -8,9 +8,20 @@ rand = "0.8.5" serde = { version = "1.0", features = ["derive"] } chrono = { version= "0.4.38", features = ["serde"]} serde_json = "1.0.128" -actix-web = "4" +actix-web = "4.9.0" env_logger = "0.8" log = "0.4" +futures = "0.3.30" +async-std = "1.13.0" + +[dependencies.uuid] +version = "1.10.0" +features = [ + "v4", # Lets you generate random UUIDs + "fast-rng", # Use a faster (but still sufficiently random) RNG + "macro-diagnostics", # Enable better diagnostics for compile-time UUIDs + "serde" +] [dev-dependencies] proptest = "1.0.0" diff --git a/rust/src/web.rs b/rust/src/web.rs index 257817d..45540e4 100644 --- a/rust/src/web.rs +++ b/rust/src/web.rs @@ -1,20 +1,25 @@ +use actix_web::{ + get, http::header::ContentType, middleware::Logger, post, web, App, HttpResponse, HttpServer, + Responder, +}; +use async_std::task; +use futures::future::join_all; +use futures::try_join; +use serde::{Deserialize, Serialize}; use std::{ + collections::HashMap, env::args, io::{stdin, stdout, IsTerminal}, - sync::Mutex, + sync::{Arc, Mutex}, }; - -use actix_web::{ - get, http::header::ContentType, post, web, App, HttpResponse, HttpServer, Responder, -}; -use serde::{Deserialize, Serialize}; +use uuid::Uuid; #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] struct Registration { url: String, } -trait AppState { +trait AppState: Send + Sync { fn register(&mut self, registration: &Registration) -> RegistrationResult; } @@ -29,9 +34,51 @@ enum RegistrationFailure { UrlAlreadyRegistered { url: String }, } +#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] +struct Client { + id: Uuid, + url: String, +} + +#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] +struct State { + clients: HashMap<String, Client>, +} + +impl State { + fn new() -> Self { + Self { + clients: HashMap::new(), + } + } +} + +impl AppState for State { + fn register(&mut self, registration: &Registration) -> RegistrationResult { + if self.clients.contains_key(®istration.url) { + RegistrationResult::UrlAlreadyRegistered { + url: registration.url.clone(), + } + } else { + let id = Uuid::new_v4(); + self.clients.insert( + registration.url.clone(), + Client { + id, + url: registration.url.clone(), + }, + ); + RegistrationResult::RegistrationSuccess { + id: id.to_string(), + url: registration.url.clone(), + } + } + } +} + #[post("/register")] async fn register( - app_state: web::Data<Mutex<dyn AppState>>, + app_state: web::Data<Arc<Mutex<dyn AppState>>>, registration: web::Json<Registration>, ) -> impl Responder { let result = app_state.lock().unwrap().register(®istration); @@ -43,16 +90,43 @@ async fn register( #[actix_web::main] async fn main() -> std::io::Result<()> { - HttpServer::new(|| App::new().service(register)) - .bind(("127.0.0.1", 8080))? - .run() - .await + let app_state = Arc::new(Mutex::new(State::new())); + let send_state: Arc<Mutex<State>> = app_state.clone(); + let http_state: Arc<Mutex<dyn AppState>> = app_state.clone(); + env_logger::init(); + // let it run in the background + task::spawn(async move { send_tests(send_state).await }); + HttpServer::new(move || { + App::new() + .wrap(Logger::default()) + .app_data(web::Data::new(http_state.clone())) + .service(register) + }) + .bind(("127.0.0.1", 8080))? + .run() + .await +} + +async fn send_tests(clients: Arc<Mutex<State>>) -> ! { + loop { + task::sleep(std::time::Duration::from_secs(1)).await; + let clients = clients.lock().unwrap(); + for client in clients.clients.values() { + send_test(client); + } + } +} + +fn send_test(client: &Client) -> Result<(), String> { + println!("Sending test to {}", client.url); + Ok(()) } #[cfg(test)] mod app_tests { use std::sync::Arc; + use actix_web::http::header::TryIntoHeaderValue; use actix_web::{body, http::header::ContentType, middleware::Logger, test, App}; use super::*; @@ -70,9 +144,9 @@ mod app_tests { impl AppState for DummyAppState { fn register(&mut self, registration: &Registration) -> RegistrationResult { if self.id == "" { - return RegistrationResult::UrlAlreadyRegistered { + RegistrationResult::UrlAlreadyRegistered { url: registration.url.clone(), - }; + } } else { RegistrationResult::RegistrationSuccess { id: self.id.clone(), @@ -88,12 +162,12 @@ mod app_tests { let dummy_state: Arc<Mutex<dyn AppState>> = Arc::new(Mutex::new(DummyAppState::new(id.clone()))); // FIXME should only be called once, move to setup - // env_logger::init(); + env_logger::init(); let app = test::init_service( App::new() .wrap(Logger::default()) - .app_data(web::Data::from(dummy_state)) + .app_data(web::Data::new(dummy_state)) .service(register), ) .await; @@ -117,25 +191,19 @@ mod app_tests { } #[actix_web::test] - async fn post_registration_fails_given_url_is_already_registered() { + async fn post_registration_returns_400_when_register_fails() { let dummy_state: Arc<Mutex<dyn AppState>> = Arc::new(Mutex::new(DummyAppState::new("".to_string()))); let app = test::init_service( App::new() .wrap(Logger::default()) - .app_data(web::Data::from(dummy_state)) + .app_data(web::Data::new(dummy_state)) .service(register), ) .await; let url = "http://192.168.1.1".to_string(); - let _ = test::TestRequest::post() - .uri("/register") - .set_json(Registration { url: url.clone() }) - .insert_header(ContentType::json()) - .to_request(); - // second request with the same URL let req = test::TestRequest::post() .uri("/register") .set_json(Registration { url: url.clone() }) @@ -145,50 +213,27 @@ mod app_tests { let resp = test::call_service(&app, req).await; assert!(resp.status().is_client_error()); - - let body = resp.into_body(); - let bytes = body::to_bytes(body).await; assert_eq!( - RegistrationResult::UrlAlreadyRegistered { url }, - serde_json::from_slice(&bytes.unwrap()).unwrap() + ContentType::json().try_into_value().unwrap(), + resp.headers().get("content-type").unwrap() ); } - #[actix_web::test] - async fn post_registration_fails_given_url_is_already_registered() { - let id = "0123456789abcdef0123456789abcdef".to_string(); - let id_generator: Arc<dyn IdGenerator> = Arc::new(ConstantIdGenerator::new(id.clone())); + #[test] + async fn app_does_not_register_same_url_twice() { + let mut app_state = State::new(); + let registration = Registration { + url: "http://1.2.3.4".to_string(), + }; - let app = test::init_service( - App::new() - .wrap(Logger::default()) - .app_data(web::Data::from(id_generator)) - .service(register), - ) - .await; - let url = "http://192.168.1.1".to_string(); - let _ = test::TestRequest::post() - .uri("/register") - .set_json(Registration { url: url.clone() }) - .insert_header(ContentType::json()) - .to_request(); - - // second request with the same URL - let req = test::TestRequest::post() - .uri("/register") - .set_json(Registration { url: url.clone() }) - .insert_header(ContentType::json()) - .to_request(); - - let resp = test::call_service(&app, req).await; - - assert!(resp.status().is_client_error()); + app_state.register(®istration); + let result = app_state.register(®istration); - let body = resp.into_body(); - let bytes = body::to_bytes(body).await; assert_eq!( - RegistrationResult::Failure(RegistrationFailure::UrlAlreadyRegistered { url }), - serde_json::from_slice(&bytes.unwrap()).unwrap() + RegistrationResult::UrlAlreadyRegistered { + url: "http://1.2.3.4".to_string() + }, + result ); } } |
