rust_web_server/blocklist/
mod.rs1#[cfg(test)]
25mod tests;
26
27use std::sync::atomic::{AtomicUsize, Ordering};
28use std::sync::{Mutex, OnceLock};
29
30use crate::application::Application;
31use crate::error::{AppError, IntoResponse};
32use crate::middleware::Middleware;
33use crate::request::Request;
34use crate::response::Response;
35use crate::server::ConnectionInfo;
36
37pub struct Blocklist {
39 denied: Mutex<Vec<String>>,
40 count: AtomicUsize,
44}
45
46impl Blocklist {
47 fn new() -> Self {
48 Blocklist { denied: Mutex::new(Vec::new()), count: AtomicUsize::new(0) }
49 }
50
51 pub fn block(&self, ip: &str) {
53 let mut guard = self.denied.lock().unwrap();
54 if !guard.iter().any(|e| e == ip) {
55 guard.push(ip.to_string());
56 }
57 self.count.store(guard.len(), Ordering::Relaxed);
58 }
59
60 pub fn unblock(&self, ip: &str) {
62 let mut guard = self.denied.lock().unwrap();
63 guard.retain(|e| e != ip);
64 self.count.store(guard.len(), Ordering::Relaxed);
65 }
66
67 pub fn is_blocked(&self, ip: &str) -> bool {
69 if self.count.load(Ordering::Relaxed) == 0 {
70 return false;
71 }
72 self.denied.lock().unwrap().iter().any(|e| e == ip)
73 }
74
75 pub fn list(&self) -> Vec<String> {
77 self.denied.lock().unwrap().clone()
78 }
79
80 pub fn clear(&self) {
82 let mut guard = self.denied.lock().unwrap();
83 guard.clear();
84 self.count.store(0, Ordering::Relaxed);
85 }
86}
87
88static INSTANCE: OnceLock<Blocklist> = OnceLock::new();
89
90pub fn global() -> &'static Blocklist {
92 INSTANCE.get_or_init(Blocklist::new)
93}
94
95pub struct BlocklistLayer;
100
101impl Middleware for BlocklistLayer {
102 fn accepts_streamed_body(&self, _request: &Request, _connection: &ConnectionInfo) -> bool {
104 true
105 }
106
107
108 fn handle(
109 &self,
110 request: &Request,
111 connection: &ConnectionInfo,
112 next: &dyn Application,
113 ) -> Result<Response, String> {
114 if global().is_blocked(&connection.client.ip) {
115 return Ok(AppError::Forbidden.into_response());
116 }
117 next.execute(request, connection)
118 }
119
120 fn runs_inline(&self, _request: &Request, _connection: &ConnectionInfo) -> bool {
122 true
123 }
124}