webserver_colin_ugo/server/
handler.rs

1use std::fs;
2use std::io::{BufRead, BufReader};
3use std::net::{TcpListener, TcpStream};
4use std::path::Path;
5use std::sync::Arc;
6use std::thread;
7use std::time::Duration;
8use std::io::Write;
9
10use crate::config::ServerConfig;
11use crate::http::{
12    HttpMethod, StatusCode, 
13    send_get_response, send_head_response, 
14    send_not_found_response, 
15    send_method_not_allowed_response,
16    send_internal_server_error_response,
17    send_not_implemented_response,
18    send_http_version_not_supported_response,
19    send_forbidden_response,
20};
21use crate::utils::{get_content_type, Logger, StatsManager};
22
23/// Cette structure représente un serveur web simple.
24pub struct WebServer {
25    config: ServerConfig,
26    logger: Arc<Logger>,
27    stats_manager: Arc<StatsManager>,
28}
29
30impl WebServer {
31    /// Crée une nouvelle instance de WebServer avec la configuration fournie.
32    ///
33    /// # Arguments
34    ///
35    /// * `config` - Une instance de `ServerConfig` contenant la configuration du serveur.
36    ///
37    /// # Retourne
38    ///
39    /// * Une nouvelle instance de `WebServer`.
40    pub fn new(config: ServerConfig) -> Self {
41        let logger = match Logger::new(&config) {
42            Ok(logger) => Arc::new(logger),
43            Err(e) => {
44                eprintln!("Erreur lors de l'initialisation du fichier de log: {}. La journalisation des requêtes sera désactivée.", e);
45                Arc::new(Logger::new(&ServerConfig {
46                    log_file: "/dev/null".to_string(),
47                    ..config.clone()
48                }).unwrap_or_else(|_| panic!("Impossible de créer un logger fallback")))
49            }
50        };
51        
52        let stats_manager = Arc::new(StatsManager::new());
53        
54        Self { config, logger, stats_manager }
55    }
56
57    /// Démarre le serveur et écoute les connexions entrantes.
58    pub fn run(&self) {
59        let bind_address = self.config.bind_address();
60        let listener = match TcpListener::bind(&bind_address) {
61            Ok(listener) => listener,
62            Err(e) => {
63                let _ = self.logger.log_message(&format!("Erreur lors de la liaison à l'adresse {}: {}", bind_address, e));
64                eprintln!("Erreur lors de la liaison à l'adresse {}: {}", bind_address, e);
65                return;
66            }
67        };
68
69        let _ = self.logger.log_message(&format!("Serveur démarré sur {}", bind_address));
70        let _ = self.logger.log_message(&format!("Dossier public: {}", self.config.public_dir));
71
72        for stream in listener.incoming() {
73            match stream {
74                Ok(stream) => {
75                    let config = self.config.clone();
76                    let logger = Arc::clone(&self.logger);
77                    let stats_manager = Arc::clone(&self.stats_manager);
78                    
79                    thread::spawn(move || {
80                        let server = WebServer { config, logger, stats_manager };
81                        server.handle_connection(stream);
82                    });
83                }
84                Err(e) => {
85                    let _ = self.logger.log_message(&format!("Erreur de connexion: {}", e));
86                }
87            }
88        }
89    }
90
91    /// Gère une connexion entrante.
92    ///
93    /// # Arguments
94    ///
95    /// * `stream` - Un flux TCP représentant la connexion entrante.
96    fn handle_connection(&self, mut stream: TcpStream) {
97        let peer_addr = match stream.peer_addr() {
98            Ok(addr) => addr,
99            Err(_) => {
100                let _ = self.logger.log_message("Impossible d'obtenir l'adresse du client");
101                return;
102            }
103        };
104
105        let keep_alive_timeout = Duration::from_secs(30);
106        
107        loop {
108            if let Err(e) = stream.set_read_timeout(Some(keep_alive_timeout)) {
109                let _ = self.logger.log_message(&format!("Erreur lors de la configuration du timeout: {}", e));
110                break;
111            }
112
113            let buf_reader = BufReader::new(&stream);
114            let http_request: Result<Vec<String>, _> = buf_reader
115                .lines()
116                .take_while(|line| match line {
117                    Ok(l) => !l.is_empty(),
118                    Err(_) => false,
119                })
120                .collect();
121
122            let http_request = match http_request {
123                Ok(req) if !req.is_empty() => req,
124                _ => {
125                    break;
126                }
127            };
128
129            let request_line = http_request.first().unwrap();
130            let mut parts = request_line.split_whitespace();
131            let method = parts.next().unwrap_or("");
132            let path = parts.next().unwrap_or("/");
133            let version = parts.next().unwrap_or("");
134
135            self.stats_manager.increment_request_count();
136
137            if !version.starts_with("HTTP/1.") {
138                let _ = self.logger.log_request(&peer_addr, method, path, StatusCode::HTTP_VERSION_NOT_SUPPORTED);
139                let _ = self.logger.log_message(&format!("Version HTTP non supportée: {}", version));
140                
141                self.stats_manager.increment_error_count("505 HTTP Version Not Supported");
142
143                let _ = send_http_version_not_supported_response(&mut stream, "close");
144                break;
145            }
146
147            let connection_header = http_request
148                .iter()
149                .find(|line| line.to_lowercase().starts_with("connection:"))
150                .map(|line| line.split(':').nth(1).unwrap_or("").trim().to_lowercase())
151                .unwrap_or_else(|| "close".to_string());
152
153            if method == "GET" {
154                if path == "/stats" {
155                    self.handle_stats_request(&peer_addr, &mut stream, &connection_header);
156                } else {
157                    self.handle_get_request(&peer_addr, path, &mut stream, &connection_header);
158                }
159            } else if method == "HEAD" {
160                self.handle_head_request(&peer_addr, path, &mut stream, &connection_header);
161            } else if HttpMethod::from_str(method).is_some() {
162                let _ = self.logger.log_request(&peer_addr, method, path, StatusCode::NOT_IMPLEMENTED);
163                let _ = self.logger.log_message(&format!("Méthode non implémentée: {}", method));
164                
165                self.stats_manager.increment_error_count("501 Not Implemented");
166                
167                let _ = send_not_implemented_response(&mut stream, &connection_header);
168            } else {
169                let _ = self.logger.log_request(&peer_addr, method, path, StatusCode::METHOD_NOT_ALLOWED);
170                let _ = self.logger.log_message(&format!("Méthode non autorisée: {}", method));
171                
172                self.stats_manager.increment_error_count("405 Method Not Allowed");
173                
174                let _ = send_method_not_allowed_response(&mut stream, &connection_header);
175            }
176
177            if connection_header != "keep-alive" {
178                break;
179            }
180            
181        }
182        
183        let _ = self.logger.log_message(&format!("Connexion fermée pour {}", peer_addr));
184    }
185
186    /// Gère une requête pour les statistiques du serveur.
187    ///
188    /// # Arguments
189    ///
190    /// * `peer_addr` - L'adresse du client.
191    /// * `stream` - Un flux TCP représentant la connexion.
192    /// * `connection` - La valeur de l'en-tête "Connection" de la requête.
193    fn handle_stats_request(&self, peer_addr: &std::net::SocketAddr, stream: &mut TcpStream, connection: &str) {
194        match self.stats_manager.to_json() {
195            Ok(json) => {
196                let _ = self.logger.log_request(peer_addr, "GET", "/stats", StatusCode::OK);
197                let _ = send_get_response(stream, StatusCode::OK, "application/json", json, connection);
198            }
199            Err(e) => {
200                let _ = self.logger.log_request(peer_addr, "GET", "/stats", StatusCode::INTERNAL_SERVER_ERROR);
201                let _ = self.logger.log_message(&format!("Erreur lors de la génération des statistiques: {}", e));
202                
203                self.stats_manager.increment_error_count("500 Internal Server Error");
204                
205                let _ = send_internal_server_error_response(stream, connection);
206            }
207        }
208    }
209
210    /// Gère une requête GET.
211    ///
212    /// # Arguments
213    ///
214    /// * `peer_addr` - L'adresse du client.
215    /// * `path` - Le chemin de la ressource demandée.
216    /// * `stream` - Un flux TCP représentant la connexion.
217    /// * `connection` - La valeur de l'en-tête "Connection" de la requête.
218    fn handle_get_request(&self, peer_addr: &std::net::SocketAddr, path: &str, stream: &mut TcpStream, connection: &str) {
219        if path.contains("..") {
220            let _ = self.logger.log_request(peer_addr, "GET", path, StatusCode::FORBIDDEN);
221            let _ = self.logger.log_message(&format!("Tentative d'accès non autorisé: {}", path));
222            
223            self.stats_manager.increment_error_count("403 Forbidden");
224            
225            let _ = send_forbidden_response(stream, connection);
226            return;
227        }
228        
229        let file_path = if path == "/" || path == format!("/{}", self.config.index_file) {
230            format!("src/{}", self.config.index_file)
231        } else {
232            format!("{}{}", self.config.public_dir, path)
233        };
234        
235        if Path::new(&file_path).exists() && Path::new(&file_path).is_file() {
236            let content_type = get_content_type(&self.config, &file_path);
237            
238            let is_binary = self.is_binary_content_type(content_type);
239            
240            if is_binary {
241                match fs::read(&file_path) {
242                    Ok(contents) => {
243                        let _ = self.logger.log_request(peer_addr, "GET", path, StatusCode::OK);
244                        let _ = self.send_binary_response(stream, StatusCode::OK, content_type, &contents, connection);
245                    }
246                    Err(e) => {
247                        let _ = self.logger.log_request(peer_addr, "GET", path, StatusCode::INTERNAL_SERVER_ERROR);
248                        let _ = self.logger.log_message(&format!("Erreur de lecture du fichier binaire: {}", e));
249                        
250                        self.stats_manager.increment_error_count("500 Internal Server Error");
251                        
252                        let _ = send_internal_server_error_response(stream, connection);
253                    }
254                }
255            } else {
256                match fs::read_to_string(&file_path) {
257                    Ok(contents) => {
258                        let _ = self.logger.log_request(peer_addr, "GET", path, StatusCode::OK);
259                        
260                        let _ = send_get_response(stream, StatusCode::OK, content_type, contents, connection);
261                    }
262                    Err(e) => {
263                        let _ = self.logger.log_request(peer_addr, "GET", path, StatusCode::INTERNAL_SERVER_ERROR);
264                        let _ = self.logger.log_message(&format!("Erreur de lecture du fichier: {}", e));
265                        
266                        self.stats_manager.increment_error_count("500 Internal Server Error");
267                        
268                        let _ = send_internal_server_error_response(stream, connection);
269                    }
270                }
271            }
272        } else {
273            let _ = self.logger.log_request(peer_addr, "GET", &path, StatusCode::NOT_FOUND);
274            let _ = self.logger.log_message(&format!("Fichier non trouvé: {}", file_path));
275            
276            self.stats_manager.increment_error_count("404 Not Found");
277            
278            self.send_not_found_response(stream, connection);
279        }
280    }
281    
282    /// Vérifie si le type de contenu est binaire.
283    ///
284    /// # Arguments
285    ///
286    /// * `content_type` - Le type de contenu à vérifier.
287    ///
288    /// # Retourne
289    ///
290    /// * `true` si le type de contenu est binaire, sinon `false`.
291    fn is_binary_content_type(&self, content_type: &str) -> bool {
292        content_type.starts_with("image/") || 
293            content_type.starts_with("audio/") || 
294            content_type.starts_with("video/") || 
295            content_type.starts_with("application/") && content_type != "application/javascript"
296    }
297    
298    /// Envoie une réponse binaire au client
299    ///
300    /// # Arguments
301    ///
302    /// * `stream` - Un flux TCP représentant la connexion.
303    /// * `status_line` - La ligne de statut HTTP (ex: "HTTP/1.1 200 OK").
304    /// * `content_type` - Le type de contenu de la réponse (ex: "image/png").
305    /// * `contents` - Le contenu binaire à envoyer.
306    /// * `connection` - La valeur de l'en-tête "Connection" de la requête.
307    ///
308    /// # Retourne
309    ///
310    /// * `Ok(())` si l'envoi a réussi, sinon une erreur.
311    fn send_binary_response(&self, 
312        stream: &mut TcpStream, 
313        status_line: &str, 
314        content_type: &str, 
315        contents: &[u8], 
316        connection: &str
317    ) -> std::io::Result<()> {
318        let length = contents.len();
319        let date = crate::utils::get_http_date();
320        let server = crate::utils::get_server_version();
321        
322        // Construire l'en-tête de la réponse
323        let header = format!(
324            "{}\r\nServer: {}\r\nDate: {}\r\nContent-Length: {}\r\nContent-Type: {}\r\nConnection: {}\r\n\r\n",
325            status_line, server, date, length, content_type, connection
326        );
327        
328        // Écrire l'en-tête
329        stream.write_all(header.as_bytes())?;
330        
331        // Écrire le contenu binaire
332        stream.write_all(contents)
333    }
334
335    /// Gère une requête HEAD.
336    ///
337    /// # Arguments
338    ///
339    /// * `peer_addr` - L'adresse du client.
340    /// * `path` - Le chemin de la ressource demandée.
341    /// * `stream` - Un flux TCP représentant la connexion.
342    /// * `connection` - La valeur de l'en-tête "Connection" de la requête.
343    fn handle_head_request(&self, peer_addr: &std::net::SocketAddr, path: &str, stream: &mut TcpStream, connection: &str) {
344        if path.contains("..") {
345            let _ = self.logger.log_request(peer_addr, "HEAD", path, StatusCode::FORBIDDEN);
346            let _ = self.logger.log_message(&format!("Tentative d'accès non autorisé: {}", path));
347            
348            self.stats_manager.increment_error_count("403 Forbidden");
349            
350            let _ = send_head_response(stream, StatusCode::FORBIDDEN, "text/html", connection);
351            return;
352        }
353        
354        let file_path = if path == "/" || path == format!("/{}", self.config.index_file) {
355            format!("src/{}", self.config.index_file)
356        } else {
357            format!("{}{}", self.config.public_dir, path)
358        };
359        
360        if Path::new(&file_path).exists() && Path::new(&file_path).is_file() {
361            let content_type = get_content_type(&self.config, &file_path);
362            let _ = self.logger.log_request(peer_addr, "HEAD", path, StatusCode::OK);
363            
364            let _ = send_head_response(stream, StatusCode::OK, content_type, connection);
365        } else {
366            let _ = self.logger.log_request(peer_addr, "HEAD", path, StatusCode::NOT_FOUND);
367            let _ = self.logger.log_message(&format!("Fichier non trouvé: {}", file_path));
368            
369            self.stats_manager.increment_error_count("404 Not Found");
370            
371            let _ = send_head_response(stream, StatusCode::NOT_FOUND, "text/html", connection);
372        }
373    }
374    
375    /// Envoie une réponse 404 personnalisée ou par défaut
376    ///
377    /// # Arguments
378    ///
379    /// * `stream` - Un flux TCP représentant la connexion.
380    /// * `connection` - La valeur de l'en-tête "Connection" de la requête.
381    fn send_not_found_response(&self, stream: &mut TcpStream, connection: &str) {
382        let custom_404_path = format!("{}/{}", self.config.public_dir, self.config.not_found_page);
383        
384        if Path::new(&custom_404_path).exists() && Path::new(&custom_404_path).is_file() {
385            match fs::read_to_string(&custom_404_path) {
386                Ok(contents) => {
387                    let content_type = get_content_type(&self.config, &custom_404_path);
388                    let _ = send_get_response(stream, StatusCode::NOT_FOUND, content_type, contents, connection);
389                }
390                Err(_) => {
391                    let _ = send_not_found_response(stream, connection);
392                }
393            }
394        } else {
395            let _ = send_not_found_response(stream, connection);
396        }
397    }
398}
399
400#[cfg(test)]
401mod tests {
402    use super::*;
403
404    #[test]
405    fn test_new_webserver() {
406        let config = ServerConfig::default();
407        let server = WebServer::new(config.clone());
408        
409        assert_eq!(server.config.host, config.host);
410        assert_eq!(server.config.port, config.port);
411        assert_eq!(server.config.public_dir, config.public_dir);
412    }
413}