From 468a5cd4be8e54ef178863a793ae0ec06bf2defe Mon Sep 17 00:00:00 2001 From: twil3akine Date: Tue, 19 Aug 2025 23:39:31 +0900 Subject: [PATCH] =?UTF-8?q?feat:=201req/s=E3=82=92=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - レート制限を導入 --- backend/src/lib.rs | 6 +-- backend/src/main.rs | 31 ++++++++++++---- backend/src/{api/mod.rs => utils/api.rs} | 0 backend/src/utils/mod.rs | 3 ++ backend/src/utils/ratelimiter.rs | 37 +++++++++++++++++++ .../src/{routing/mod.rs => utils/routing.rs} | 2 +- backend/tests/api_test.rs | 2 +- backend/tests/routing_test.rs | 4 +- 8 files changed, 68 insertions(+), 17 deletions(-) rename backend/src/{api/mod.rs => utils/api.rs} (100%) create mode 100644 backend/src/utils/mod.rs create mode 100644 backend/src/utils/ratelimiter.rs rename backend/src/{routing/mod.rs => utils/routing.rs} (98%) diff --git a/backend/src/lib.rs b/backend/src/lib.rs index 6232d93..fab870e 100644 --- a/backend/src/lib.rs +++ b/backend/src/lib.rs @@ -1,5 +1 @@ -pub mod routing; -pub mod api; - -pub use routing::{router, AppState}; -pub use api::fetch_problem; \ No newline at end of file +pub mod utils; \ No newline at end of file diff --git a/backend/src/main.rs b/backend/src/main.rs index 770d027..a5a268d 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -1,11 +1,12 @@ -use backend::{api, router, AppState}; +use backend::utils::api; +use backend::utils::ratelimiter::RateLimiter; +use backend::utils::routing::{AppState, router}; + use hyper::service::{make_service_fn, service_fn}; -use hyper::Server; -use std::net::SocketAddr; -use std::convert::{From, Infallible}; -use std::{println}; -use std::result::Result::{Err, Ok}; +use hyper::{Server, Body, Response, StatusCode}; use std::sync::Arc; +use std::net::SocketAddr; +use std::convert::Infallible; #[tokio::main] async fn main() { @@ -14,6 +15,7 @@ async fn main() { println!("Succeeded to fetch problems"); let state = Arc::new(AppState { problems, problem_models }); + let limiter = RateLimiter::new(); // Fly.io 環境変数 PORT を使用 let port: u16 = std::env::var("PORT") @@ -24,12 +26,25 @@ async fn main() { // 0.0.0.0 でバインド let addr = SocketAddr::from(([0, 0, 0, 0], port)); - let make_svc = make_service_fn(move |_conn| { + let make_svc = make_service_fn(move |conn: &hyper::server::conn::AddrStream| { + let remote_addr = conn.remote_addr().ip(); let state = state.clone(); + let limiter = limiter.clone(); + async move { Ok::<_, Infallible>(service_fn(move |req| { let state = state.clone(); - router(req, state) + let limiter = limiter.clone(); + let ip = remote_addr; + + async move { + if !limiter.check(ip).await { + let mut res = Response::new(Body::from("Too Many Requests")); + *res.status_mut() = StatusCode::TOO_MANY_REQUESTS; + return Ok::<_, Infallible>(res) + } + router(req, state).await + } })) } }); diff --git a/backend/src/api/mod.rs b/backend/src/utils/api.rs similarity index 100% rename from backend/src/api/mod.rs rename to backend/src/utils/api.rs diff --git a/backend/src/utils/mod.rs b/backend/src/utils/mod.rs new file mode 100644 index 0000000..1e5148d --- /dev/null +++ b/backend/src/utils/mod.rs @@ -0,0 +1,3 @@ +pub mod api; +pub mod ratelimiter; +pub mod routing; \ No newline at end of file diff --git a/backend/src/utils/ratelimiter.rs b/backend/src/utils/ratelimiter.rs new file mode 100644 index 0000000..63e0025 --- /dev/null +++ b/backend/src/utils/ratelimiter.rs @@ -0,0 +1,37 @@ +use std::collections::HashMap; +use std::time::{Duration, Instant}; +use std::net::IpAddr; +use std::sync::Arc; +use tokio::sync::Mutex; + +#[derive(Clone)] +pub struct RateLimiter { + pub last_request: Arc>>, + pub ttl: Duration, +} + +impl RateLimiter { + pub fn new() -> Self { + Self { + last_request: Arc::new(Mutex::new(HashMap::new())), + ttl: Duration::from_secs(600), + } + } + + pub async fn check(&self, ip: IpAddr) -> bool { + let mut map = self.last_request.lock().await; + let now = Instant::now(); + + map.retain(|_, &mut last| now.duration_since(last) < self.ttl); + + match map.get(&ip) { + Some(&last) if now.duration_since(last) < Duration::from_secs(1) => { + false + }, + _ => { + map.insert(ip, now); + true + } + } + } +} \ No newline at end of file diff --git a/backend/src/routing/mod.rs b/backend/src/utils/routing.rs similarity index 98% rename from backend/src/routing/mod.rs rename to backend/src/utils/routing.rs index 3706630..f364bda 100644 --- a/backend/src/routing/mod.rs +++ b/backend/src/utils/routing.rs @@ -9,7 +9,7 @@ use rand; use rand::seq::IteratorRandom; use serde::Serialize; -use crate::api::{Problem, ProblemModel}; +use crate::utils::api::{Problem, ProblemModel}; #[derive(Clone)] pub struct AppState { diff --git a/backend/tests/api_test.rs b/backend/tests/api_test.rs index f9942b8..76b4084 100644 --- a/backend/tests/api_test.rs +++ b/backend/tests/api_test.rs @@ -1,4 +1,4 @@ -use backend::api::{Problem, ProblemModel}; +use backend::utils::api::{Problem, ProblemModel}; use std::collections::HashMap; /// fetch_problem のモック版 diff --git a/backend/tests/routing_test.rs b/backend/tests/routing_test.rs index f61bebd..71b66df 100644 --- a/backend/tests/routing_test.rs +++ b/backend/tests/routing_test.rs @@ -1,7 +1,7 @@ #![allow(unused)] -use backend::routing::{router, AppState}; -use backend::api::{Problem, ProblemModel}; +use backend::utils::routing::{router, AppState}; +use backend::utils::api::{Problem, ProblemModel}; use hyper::{Body, Method, Request, StatusCode}; use std::assert; use std::collections::HashMap;