Add rate limiting functionality

This commit is contained in:
Ben Grant 2023-05-13 22:03:52 +10:00
parent 4676abf6a9
commit 983424e17b
3 changed files with 197 additions and 7 deletions

173
backend/Cargo.lock generated
View file

@ -604,7 +604,9 @@ dependencies = [
"serde_json", "serde_json",
"sql-adaptor", "sql-adaptor",
"tokio", "tokio",
"tower",
"tower-http", "tower-http",
"tower_governor",
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",
] ]
@ -702,6 +704,19 @@ dependencies = [
"syn 2.0.15", "syn 2.0.15",
] ]
[[package]]
name = "dashmap"
version = "5.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc"
dependencies = [
"cfg-if",
"hashbrown 0.12.3",
"lock_api",
"once_cell",
"parking_lot_core 0.9.7",
]
[[package]] [[package]]
name = "der" name = "der"
version = "0.5.1" version = "0.5.1"
@ -840,6 +855,16 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "forwarded-header-value"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8835f84f38484cc86f110a805655697908257fb9a7af005234060891557198e9"
dependencies = [
"nonempty",
"thiserror",
]
[[package]] [[package]]
name = "futures" name = "futures"
version = "0.3.28" version = "0.3.28"
@ -848,6 +873,7 @@ checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40"
dependencies = [ dependencies = [
"futures-channel", "futures-channel",
"futures-core", "futures-core",
"futures-executor",
"futures-io", "futures-io",
"futures-sink", "futures-sink",
"futures-task", "futures-task",
@ -889,7 +915,7 @@ checksum = "a604f7a68fbf8103337523b1fadc8ade7361ee3f112f7c680ad179651616aed5"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"lock_api", "lock_api",
"parking_lot", "parking_lot 0.11.2",
] ]
[[package]] [[package]]
@ -913,6 +939,17 @@ dependencies = [
"waker-fn", "waker-fn",
] ]
[[package]]
name = "futures-macro"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.15",
]
[[package]] [[package]]
name = "futures-sink" name = "futures-sink"
version = "0.3.28" version = "0.3.28"
@ -925,6 +962,12 @@ version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"
[[package]]
name = "futures-timer"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c"
[[package]] [[package]]
name = "futures-util" name = "futures-util"
version = "0.3.28" version = "0.3.28"
@ -934,6 +977,7 @@ dependencies = [
"futures-channel", "futures-channel",
"futures-core", "futures-core",
"futures-io", "futures-io",
"futures-macro",
"futures-sink", "futures-sink",
"futures-task", "futures-task",
"memchr", "memchr",
@ -975,6 +1019,24 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "governor"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c390a940a5d157878dd057c78680a33ce3415bcd05b4799509ea44210914b4d5"
dependencies = [
"cfg-if",
"dashmap",
"futures",
"futures-timer",
"no-std-compat",
"nonzero_ext",
"parking_lot 0.12.1",
"quanta",
"rand",
"smallvec",
]
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.12.3" version = "0.12.3"
@ -1295,6 +1357,15 @@ dependencies = [
"value-bag", "value-bag",
] ]
[[package]]
name = "mach"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "matchers" name = "matchers"
version = "0.1.0" version = "0.1.0"
@ -1367,6 +1438,12 @@ dependencies = [
"tempfile", "tempfile",
] ]
[[package]]
name = "no-std-compat"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c"
[[package]] [[package]]
name = "nom" name = "nom"
version = "7.1.3" version = "7.1.3"
@ -1377,6 +1454,18 @@ dependencies = [
"minimal-lexical", "minimal-lexical",
] ]
[[package]]
name = "nonempty"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7"
[[package]]
name = "nonzero_ext"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21"
[[package]] [[package]]
name = "nu-ansi-term" name = "nu-ansi-term"
version = "0.46.0" version = "0.46.0"
@ -1555,7 +1644,17 @@ checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
dependencies = [ dependencies = [
"instant", "instant",
"lock_api", "lock_api",
"parking_lot_core", "parking_lot_core 0.8.6",
]
[[package]]
name = "parking_lot"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [
"lock_api",
"parking_lot_core 0.9.7",
] ]
[[package]] [[package]]
@ -1572,6 +1671,19 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "parking_lot_core"
version = "0.9.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521"
dependencies = [
"cfg-if",
"libc",
"redox_syscall 0.2.16",
"smallvec",
"windows-sys 0.45.0",
]
[[package]] [[package]]
name = "paste" name = "paste"
version = "1.0.12" version = "1.0.12"
@ -1743,6 +1855,22 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9e1dcb320d6839f6edb64f7a4a59d39b30480d4d1765b56873f7c858538a5fe" checksum = "e9e1dcb320d6839f6edb64f7a4a59d39b30480d4d1765b56873f7c858538a5fe"
[[package]]
name = "quanta"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20afe714292d5e879d8b12740aa223c6a88f118af41870e8b6196e39a02238a8"
dependencies = [
"crossbeam-utils",
"libc",
"mach",
"once_cell",
"raw-cpuid",
"wasi 0.10.0+wasi-snapshot-preview1",
"web-sys",
"winapi",
]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.27" version = "1.0.27"
@ -1782,6 +1910,15 @@ dependencies = [
"getrandom", "getrandom",
] ]
[[package]]
name = "raw-cpuid"
version = "10.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332"
dependencies = [
"bitflags",
]
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.2.16" version = "0.2.16"
@ -2237,6 +2374,15 @@ dependencies = [
"lazy_static", "lazy_static",
] ]
[[package]]
name = "signal-hook-registry"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "simdutf8" name = "simdutf8"
version = "0.1.4" version = "0.1.4"
@ -2586,7 +2732,9 @@ dependencies = [
"libc", "libc",
"mio", "mio",
"num_cpus", "num_cpus",
"parking_lot 0.12.1",
"pin-project-lite", "pin-project-lite",
"signal-hook-registry",
"socket2", "socket2",
"tokio-macros", "tokio-macros",
"windows-sys 0.48.0", "windows-sys 0.48.0",
@ -2665,6 +2813,7 @@ dependencies = [
"pin-project-lite", "pin-project-lite",
"tower-layer", "tower-layer",
"tower-service", "tower-service",
"tracing",
] ]
[[package]] [[package]]
@ -2679,6 +2828,26 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
[[package]]
name = "tower_governor"
version = "0.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c6be418f6d18863291f0a7fa1da1de71495a19a54b5fb44969136f731a47e86"
dependencies = [
"axum",
"forwarded-header-value",
"futures",
"futures-core",
"governor",
"http",
"pin-project",
"thiserror",
"tokio",
"tower",
"tower-layer",
"tracing",
]
[[package]] [[package]]
name = "tracing" name = "tracing"
version = "0.1.37" version = "0.1.37"

View file

@ -21,4 +21,6 @@ tracing = "0.1.37"
tracing-subscriber = "0.3.17" tracing-subscriber = "0.3.17"
chrono = "0.4.24" chrono = "0.4.24"
bcrypt = "0.14.0" bcrypt = "0.14.0"
tower-http = { version = "0.4.0", features = ["cors"] } tower-http = { version = "0.4.0", features = ["cors", "trace"] }
tower_governor = "0.0.4"
tower = "0.4.13"

View file

@ -1,15 +1,18 @@
use std::{env, net::SocketAddr, sync::Arc}; use std::{env, net::SocketAddr, sync::Arc};
use axum::{ use axum::{
error_handling::HandleErrorLayer,
extract, extract,
http::{HeaderValue, Method}, http::{HeaderValue, Method},
routing::{get, patch, post}, routing::{get, patch, post},
Router, Server, BoxError, Router, Server,
}; };
use routes::*; use routes::*;
use sql_adaptor::SqlAdaptor; use sql_adaptor::SqlAdaptor;
use tokio::sync::Mutex; use tokio::sync::Mutex;
use tower_http::cors::CorsLayer; use tower::ServiceBuilder;
use tower_governor::{errors::display_error, governor::GovernorConfigBuilder, GovernorLayer};
use tower_http::{cors::CorsLayer, trace::TraceLayer};
mod errors; mod errors;
mod payloads; mod payloads;
@ -32,6 +35,7 @@ async fn main() {
adaptor: SqlAdaptor::new().await, adaptor: SqlAdaptor::new().await,
})); }));
// CORS configuration
let cors = CorsLayer::new() let cors = CorsLayer::new()
.allow_methods([Method::GET, Method::POST, Method::PATCH]) .allow_methods([Method::GET, Method::POST, Method::PATCH])
.allow_origin( .allow_origin(
@ -44,6 +48,19 @@ async fn main() {
.unwrap(), .unwrap(),
); );
// Rate limiting configuration (using tower_governor)
// From the docs: Allows bursts with up to eight requests and replenishes
// one element after 500ms, based on peer IP.
let governor_config = Box::new(GovernorConfigBuilder::default().finish().unwrap());
let rate_limit = ServiceBuilder::new()
// Handle errors from governor and convert into HTTP responses
.layer(HandleErrorLayer::new(|e: BoxError| async move {
display_error(e)
}))
.layer(GovernorLayer {
config: Box::leak(governor_config),
});
let app = Router::new() let app = Router::new()
.route("/", get(get_root)) .route("/", get(get_root))
.route("/stats", get(get_stats)) .route("/stats", get(get_stats))
@ -53,7 +70,9 @@ async fn main() {
.route("/event/:event_id/people/:person_name", get(get_person)) .route("/event/:event_id/people/:person_name", get(get_person))
.route("/event/:event_id/people/:person_name", patch(update_person)) .route("/event/:event_id/people/:person_name", patch(update_person))
.with_state(shared_state) .with_state(shared_state)
.layer(cors); .layer(cors)
.layer(rate_limit)
.layer(TraceLayer::new_for_http());
let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
@ -67,7 +86,7 @@ async fn main() {
} }
); );
Server::bind(&addr) Server::bind(&addr)
.serve(app.into_make_service()) .serve(app.into_make_service_with_connect_info::<SocketAddr>())
.await .await
.unwrap(); .unwrap();
} }