diff --git a/Cargo.lock b/Cargo.lock index e9fac54..172f4eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -374,6 +374,12 @@ dependencies = [ "regex", ] +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + [[package]] name = "ctrlc" version = "3.4.2" @@ -384,6 +390,19 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown", + "lock_api", + "once_cell", + "parking_lot_core", +] + [[package]] name = "deranged" version = "0.3.11" @@ -569,6 +588,7 @@ checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", + "futures-executor", "futures-io", "futures-sink", "futures-task", @@ -591,6 +611,17 @@ 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" @@ -620,6 +651,12 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +[[package]] +name = "futures-timer" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" + [[package]] name = "futures-util" version = "0.3.30" @@ -1047,6 +1084,26 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "governor" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68a7f542ee6b35af73b06abc0dad1c1bae89964e4e253bc4b587b91c9637867b" +dependencies = [ + "cfg-if", + "dashmap", + "futures", + "futures-timer", + "no-std-compat", + "nonzero_ext", + "parking_lot", + "portable-atomic", + "quanta", + "rand", + "smallvec", + "spinning_top", +] + [[package]] name = "gql_client" version = "1.0.7" @@ -1408,6 +1465,18 @@ dependencies = [ "libc", ] +[[package]] +name = "no-std-compat" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c" + +[[package]] +name = "nonzero_ext" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21" + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -1609,6 +1678,12 @@ version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" +[[package]] +name = "portable-atomic" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" + [[package]] name = "powerfmt" version = "0.2.0" @@ -1713,6 +1788,21 @@ dependencies = [ "wasm-bindgen-futures", ] +[[package]] +name = "quanta" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ca0b7bac0b97248c40bb77288fc52029cf1459c0461ea1b05ee32ccf011de2c" +dependencies = [ + "crossbeam-utils", + "libc", + "once_cell", + "raw-cpuid", + "wasi", + "web-sys", + "winapi", +] + [[package]] name = "quote" version = "1.0.35" @@ -1752,6 +1842,15 @@ dependencies = [ "getrandom", ] +[[package]] +name = "raw-cpuid" +version = "11.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d86a7c4638d42c44551f4791a20e687dbb4c3de1f33c43dd71e355cd429def1" +dependencies = [ + "bitflags 2.4.2", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -1902,6 +2001,17 @@ dependencies = [ "yansi", ] +[[package]] +name = "rocket-governor" +version = "0.2.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32d1c108d2262e7727c25fb0ffed261b120dc9086ddcd1c03d25d92b7b21725a" +dependencies = [ + "governor", + "lazy_static", + "rocket", +] + [[package]] name = "rocket_codegen" version = "0.5.0" @@ -2172,6 +2282,7 @@ version = "0.1.0" dependencies = [ "anyhow", "rocket", + "rocket-governor", "rocket_cors", "serde_json", "smithe_lib", @@ -2259,6 +2370,15 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "spinning_top" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d96d2d1d716fb500937168cc09353ffdc7a012be8475ac7308e1bdf0e3923300" +dependencies = [ + "lock_api", +] + [[package]] name = "stable-pattern" version = "0.1.0" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 9096ad7..05a3447 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -10,4 +10,5 @@ rocket = "0.5.0" rocket_cors = "0.6.0" serde_json = "1" thiserror = "1" -smithe_lib = { path = "../lib" } \ No newline at end of file +smithe_lib = { path = "../lib" } +rocket-governor = "0.2.0-rc.1" \ No newline at end of file diff --git a/backend/src/main.rs b/backend/src/main.rs index b7679b5..189cc41 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -1,3 +1,4 @@ +#![allow(non_snake_case)] #[macro_use] extern crate rocket; @@ -7,6 +8,7 @@ use rocket::{ Build, Request, Rocket, }; use rocket_cors::{AllowedHeaders, AllowedOrigins}; +use rocket_governor::{Quota, RocketGovernable, RocketGovernor}; use smithe_lib::{ player::{get_all_like, get_player, get_top_two_characters}, set::{ @@ -39,75 +41,108 @@ impl<'r, 'o: 'r> Responder<'r, 'o> for Error { } #[get("/")] -fn index() -> &'static str { +fn index(_limitguard: RocketGovernor<'_, RateLimitGuard>) -> &'static str { "Hello, world!" } #[get("/")] -fn search_players(tag: String) -> Result { +fn search_players( + tag: String, + _limitguard: RocketGovernor<'_, RateLimitGuard>, +) -> Result { Ok(serde_json::to_string(&get_all_like(&tag)?)?) } #[get("/")] -fn view_player(id: i32) -> Result { +fn view_player(id: i32, _limitguard: RocketGovernor<'_, RateLimitGuard>) -> Result { // insert player page view smithe_lib::player_page_views::insert_player_page_view(id).unwrap(); Ok(serde_json::to_string(&get_player(id)?)?) } #[get("/")] -async fn get_player_tournaments(id: i32) -> Result { +async fn get_player_tournaments( + id: i32, + _limitguard: RocketGovernor<'_, RateLimitGuard>, +) -> Result { Ok(serde_json::to_string( &get_tournaments_from_requester_id(id).await?, )?) } #[get("//wins_without_dqs")] -async fn get_player_set_wins_without_dqs(id: i32) -> Result { +async fn get_player_set_wins_without_dqs( + id: i32, + _limitguard: RocketGovernor<'_, RateLimitGuard>, +) -> Result { Ok(serde_json::to_string(&get_set_wins_without_dqs(id)?)?) } #[get("//losses_without_dqs")] -async fn get_player_set_losses_without_dqs(id: i32) -> Result { +async fn get_player_set_losses_without_dqs( + id: i32, + _limitguard: RocketGovernor<'_, RateLimitGuard>, +) -> Result { Ok(serde_json::to_string(&get_set_losses_without_dqs(id)?)?) } #[get("//wins_by_dqs")] -async fn get_player_set_wins_by_dqs(id: i32) -> Result { +async fn get_player_set_wins_by_dqs( + id: i32, + _limitguard: RocketGovernor<'_, RateLimitGuard>, +) -> Result { Ok(serde_json::to_string(&get_set_wins_by_dq(id)?)?) } #[get("//losses_by_dqs")] -async fn get_player_set_losses_by_dqs(id: i32) -> Result { +async fn get_player_set_losses_by_dqs( + id: i32, + _limitguard: RocketGovernor<'_, RateLimitGuard>, +) -> Result { Ok(serde_json::to_string(&get_set_losses_by_dq(id)?)?) } #[get("//winrate")] -async fn get_player_winrate(id: i32) -> Result { +async fn get_player_winrate( + id: i32, + _limitguard: RocketGovernor<'_, RateLimitGuard>, +) -> Result { Ok(serde_json::to_string(&get_winrate(id)?)?) } #[get("//competitor_type")] -async fn get_player_competitor_type(id: i32) -> Result { +async fn get_player_competitor_type( + id: i32, + _limitguard: RocketGovernor<'_, RateLimitGuard>, +) -> Result { let ct = get_competitor_type(id)?; Ok(serde_json::to_string(&format!("{}-{}er", ct.0, ct.1))?) } // endpoint to get_top_two_characters #[get("//top_two_characters")] -async fn get_player_top_two_characters(id: i32) -> Result { +async fn get_player_top_two_characters( + id: i32, + _limitguard: RocketGovernor<'_, RateLimitGuard>, +) -> Result { Ok(serde_json::to_string(&get_top_two_characters(id)?)?) } // get sets by player id #[get("/")] -async fn get_player_sets(id: i32) -> Result { +async fn get_player_sets( + id: i32, + _limitguard: RocketGovernor<'_, RateLimitGuard>, +) -> Result { Ok(serde_json::to_string(&get_sets_per_player_id(id)?)?) } // get head to head by player id #[get("//head_to_head")] -async fn get_player_head_to_head(id: i32) -> Result { +async fn get_player_head_to_head( + id: i32, + _limitguard: RocketGovernor<'_, RateLimitGuard>, +) -> Result { Ok(serde_json::to_string(&get_head_to_head_record(id)?)?) } @@ -155,6 +190,7 @@ fn rocket() -> Rocket { get_player_competitor_type, ], ) + .register("/", catchers!(rocket_governor::rocket_governor_catcher)) .attach(cors) } @@ -163,3 +199,11 @@ async fn main() -> Result<(), Box> { let _ = rocket().launch().await?; Ok(()) } + +pub struct RateLimitGuard; + +impl<'r> RocketGovernable<'r> for RateLimitGuard { + fn quota(_method: Method, _route_name: &str) -> Quota { + Quota::per_minute(Self::nonzero(60u32)) + } +} diff --git a/backend/test-rate-limit.ps1 b/backend/test-rate-limit.ps1 new file mode 100644 index 0000000..03abb6c --- /dev/null +++ b/backend/test-rate-limit.ps1 @@ -0,0 +1,4 @@ +1..150 | ForEach-Object { + curl http://127.0.0.1:8000/ + Write-Output "Request $_" +}