diff --git a/Cargo.lock b/Cargo.lock index fb8e99c..ef46413 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -293,6 +293,15 @@ dependencies = [ "const-random", ] +[[package]] +name = "ahash" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8fd72866655d1904d6b0997d0b07ba561047d070fbe29de039031c641b61217" +dependencies = [ + "const-random", +] + [[package]] name = "aho-corasick" version = "0.7.10" @@ -509,9 +518,9 @@ checksum = "404b1fe4f65288577753b17e3b36a04596ee784493ec249bf81c7f2d2acd751c" [[package]] name = "cfg-if" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] name = "chrono" @@ -549,6 +558,7 @@ dependencies = [ "actix-web", "bytes", "chrono", + "dashmap", "encoding_rs", "futures", "image", @@ -666,6 +676,17 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "dashmap" +version = "3.11.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f260e2fc850179ef410018660006951c1b55b79e8087e87111a2c388994b9b5" +dependencies = [ + "ahash 0.3.8", + "cfg-if", + "num_cpus", +] + [[package]] name = "deflate" version = "0.8.4" @@ -1075,7 +1096,7 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e6073d0ca812575946eb5f35ff68dbe519907b25c42530389ff946dc84c6ead" dependencies = [ - "ahash", + "ahash 0.2.18", "autocfg 0.1.7", ] @@ -1271,9 +1292,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "lexical" -version = "4.2.0" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaad0ee8120fc0cf7df7e8fdbe79bf9d6189351404feb88f4e4a4bb5307bc594" +checksum = "0afaeae1c07c575338ef6809875bfea8daa9ea8b2ee381ef1f93ba0c6e32f003" dependencies = [ "cfg-if", "lexical-core", @@ -1282,12 +1303,11 @@ dependencies = [ [[package]] name = "lexical-core" -version = "0.6.7" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f86d66d380c9c5a685aaac7a11818bdfa1f733198dfd9ec09c70b762cd12ad6f" +checksum = "d7043aa5c05dd34fb73b47acb8c3708eac428de4545ea3682ed2f11293ebd890" dependencies = [ "arrayvec", - "bitflags", "cfg-if", "rustc_version", "ryu", diff --git a/Cargo.toml b/Cargo.toml index 1da4788..ea5c7b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,4 +27,5 @@ kamadak-exif = "0.5.1" lazy_static = "1.4.0" mime_guess = "2.0.3" pdf = "0.6.3" -regex = "1.4.2" \ No newline at end of file +regex = "1.4.2" +dashmap = "3.11.10" \ No newline at end of file diff --git a/src/constants.rs b/src/constants.rs index 5b10be6..7c26228 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -57,6 +57,9 @@ pub mod database_tables_names { pub const NOTIFICATIONS_TABLE: &str = "comunic_notifications"; } +/// Lifetime of limit counter (1 hour) +pub const LIMIT_COUNTER_LIFETIME: u64 = 60 * 60; + /// The account image to show for user who do not have any pub const DEFAULT_ACCOUNT_IMAGE: &str = "avatars/0Reverse.png"; diff --git a/src/controllers/server.rs b/src/controllers/server.rs index ff8580a..91e1d40 100644 --- a/src/controllers/server.rs +++ b/src/controllers/server.rs @@ -18,6 +18,7 @@ use crate::controllers::routes::{get_routes, RequestResult, Route}; use crate::controllers::routes::Method::{GET, POST}; use crate::data::config::Config; use crate::data::http_request_handler::{HttpRequestHandler, PostFile, RequestValue}; +use crate::helpers::requests_limit_helper; /// Main server functions /// @@ -237,6 +238,9 @@ async fn process_request(custom_req: CustomRequest) -> HttpResponse { } let route = route.unwrap(); + // Clean requests limit + requests_limit_helper::clean_cache().unwrap(); + // Execute the request let mut request = HttpRequestHandler::new(custom_req.req, custom_req.body); @@ -280,6 +284,10 @@ async fn process_request(custom_req: CustomRequest) -> HttpResponse { /// Given the configuration, start the server pub async fn start_server(conf: &Config) -> std::io::Result<()> { + + // Initialize limit helper + requests_limit_helper::init(); + let addr = conf.server_listen_address(); println!("Start to listen on http://{}/", addr); diff --git a/src/helpers/mod.rs b/src/helpers/mod.rs index 3b24460..8087fe1 100644 --- a/src/helpers/mod.rs +++ b/src/helpers/mod.rs @@ -15,4 +15,5 @@ pub mod movies_helper; pub mod survey_helper; pub mod comments_helper; pub mod notifications_helper; -pub mod webapp_helper; \ No newline at end of file +pub mod webapp_helper; +pub mod requests_limit_helper; \ No newline at end of file diff --git a/src/helpers/requests_limit_helper.rs b/src/helpers/requests_limit_helper.rs new file mode 100644 index 0000000..1280724 --- /dev/null +++ b/src/helpers/requests_limit_helper.rs @@ -0,0 +1,60 @@ +//! # Requests limit helper +//! +//! Handle the limitation of requests, depending on threshold criterias + +use std::sync::{Arc, Mutex}; + +use crate::constants::LIMIT_COUNTER_LIFETIME; +use crate::data::error::ResultBoxError; +use crate::utils::date_utils; + +/// Information about a IP address limitation +struct IpInfo { + time_start: u64, + count: u64, +} + +/// Limits cache +type Cache = Arc>; + +static mut LIMITS_CACHE: Option>> = None; + +/// Initialize limit cache storage +pub fn init() { + let limits_cache = Arc::new(dashmap::DashMap::new()); + let limits_cache = Some(Arc::new(Mutex::new(limits_cache))); + + unsafe { + LIMITS_CACHE = limits_cache; + } +} + +/// Get access to the cache. This resource must absolutely be released as quickly as possible +fn get_cache() -> ResultBoxError { + let cache: Cache; + + unsafe { + let guard = LIMITS_CACHE.as_ref().unwrap().lock(); + cache = guard.unwrap().clone(); + } + + Ok(cache) +} + +/// Clean cached information +pub fn clean_cache() -> ResultBoxError { + let time = date_utils::time(); + + let cache = get_cache()?; + let obsolete_ips: Vec = cache + .iter() + .filter(|k| k.time_start + LIMIT_COUNTER_LIFETIME < time) + .map(|k| k.key().to_string()) + .collect(); + + for obsolete_ip in obsolete_ips { + cache.remove(&obsolete_ip); + } + + Ok(()) +} \ No newline at end of file