From c7ecec3f7e64bfbc8dc0231d538599716e1ccf88 Mon Sep 17 00:00:00 2001 From: Arnaud Bailly Date: Fri, 4 Oct 2024 17:17:10 +0200 Subject: Spawn thread to start sending requests to clients --- rust/src/web.rs | 167 +++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 106 insertions(+), 61 deletions(-) (limited to 'rust/src') 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, +} + +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>, + app_state: web::Data>>, registration: web::Json, ) -> 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> = app_state.clone(); + let http_state: Arc> = 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>) -> ! { + 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> = 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> = 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 = 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 ); } } -- cgit v1.2.3