diff --git a/tp2/README.md b/tp2/README.md index c7943276eea6f1250c88ab74c5bde76ce835e176..9eaebf41a5a57914609b84a84d6586b4b57dc532 100644 --- a/tp2/README.md +++ b/tp2/README.md @@ -340,4 +340,117 @@ fn main() { # 3. Long exercise : a mini web server ## 3.1. Description of work -## 3.2. Hints and references + +On crée un cargo avec `cargo init`, puis on met le code ci-dessous dans `src/main.rs`, puis on fait `cargo doc` puis `cargo run` + +Liens accessibles : +- la documentation : http://localhost:8080/target/doc/exercice3/index.html +- la page d'accueil : http://localhost:8080/ +- une image png : http://localhost:8080/target/image.png + +```rs +use std::net::{TcpListener, TcpStream}; +use std::io::{BufReader, BufRead, Write, Read}; +use std::fs; +use std::path::Path; + +fn main() { + // Le serveur écoute sur localhost:8080 + let listener = TcpListener::bind("127.0.0.1:8080").unwrap(); + println!("Server listening on http://127.0.0.1:8080"); + + for stream in listener.incoming() { + match stream { + Ok(stream) => { + // Pour chaque connexion, on lance un thread pour la traiter + std::thread::spawn(|| { + handle_connection(stream); + }); + }, + Err(e) => eprintln!("Failed to establish connection: {}", e), + } + } +} + +fn handle_connection(mut stream: TcpStream) { + let mut reader = BufReader::new(&stream); + let mut request_line = String::new(); + + // Lecture de la première ligne de la requête (ex: "GET / HTTP/1.1") + if reader.read_line(&mut request_line).is_err() { + return; + } + let request_line = request_line.trim_end(); + + // Découpage de la ligne de requête + let parts: Vec<&str> = request_line.split_whitespace().collect(); + if parts.len() < 3 { + send_response(&mut stream, "HTTP/1.1 400 Bad Request", "text/plain", b"Bad Request"); + return; + } + let method = parts[0]; + let path = parts[1]; + let _version = parts[2]; + + // Seul le GET est supporté + if method != "GET" { + send_response(&mut stream, "HTTP/1.1 405 Method Not Allowed", "text/plain", b"Method Not Allowed"); + return; + } + + // On lit et jette les autres lignes d'en-tête jusqu'à une ligne vide + for line in reader.by_ref().lines() { + match line { + Ok(line) if line.trim().is_empty() => break, + Ok(_) => continue, + Err(_) => break, + } + } + + // Route par défaut pour "/" + if path == "/" { + let body = b"Hello world"; + send_response(&mut stream, "HTTP/1.1 200 OK", "text/plain", body); + return; + } + + // Construction du chemin du fichier demandé + // On enlève le '/' initial pour obtenir un chemin relatif + let file_path = &path[1..]; + match fs::read(file_path) { + Ok(contents) => { + // Détermination du Content-type selon l'extension du fichier + let content_type = match Path::new(file_path).extension().and_then(|ext| ext.to_str()) { + Some("html") => "text/html", + Some("css") => "text/css", + Some("js") => "application/javascript", + Some("jpg") => "image/jpeg", + Some("png") => "image/png", + Some("svg") => "image/svg+xml", + Some("woff") => "font/woff", + Some("woff2")=> "font/woff2", + _ => "text/plain", + }; + send_response(&mut stream, "HTTP/1.1 200 OK", content_type, &contents); + }, + Err(ref e) if e.kind() == std::io::ErrorKind::NotFound => { + send_response(&mut stream, "HTTP/1.1 404 Not Found", "text/plain", b"Not Found"); + }, + Err(_) => { + send_response(&mut stream, "HTTP/1.1 500 Internal Server Error", "text/plain", b"Internal Server Error"); + } + } +} + +fn send_response(stream: &mut TcpStream, status_line: &str, content_type: &str, body: &[u8]) { + // Construction de la réponse HTTP avec les en-têtes requis + let response = format!( + "{}\r\nContent-type: {}\r\nContent-length: {}\r\n\r\n", + status_line, + content_type, + body.len() + ); + let _ = stream.write_all(response.as_bytes()); + let _ = stream.write_all(body); +} +``` diff --git a/tp2/src/exercice1/problem1.rs b/tp2/exercice1/problem1.rs similarity index 100% rename from tp2/src/exercice1/problem1.rs rename to tp2/exercice1/problem1.rs diff --git a/tp2/src/exercice1/problem2.rs b/tp2/exercice1/problem2.rs similarity index 100% rename from tp2/src/exercice1/problem2.rs rename to tp2/exercice1/problem2.rs diff --git a/tp2/src/exercice1/problem3.rs b/tp2/exercice1/problem3.rs similarity index 100% rename from tp2/src/exercice1/problem3.rs rename to tp2/exercice1/problem3.rs diff --git a/tp2/src/exercice1/problem4.rs b/tp2/exercice1/problem4.rs similarity index 100% rename from tp2/src/exercice1/problem4.rs rename to tp2/exercice1/problem4.rs diff --git a/tp2/src/exercice1/problem5.rs b/tp2/exercice1/problem5.rs similarity index 100% rename from tp2/src/exercice1/problem5.rs rename to tp2/exercice1/problem5.rs diff --git a/tp2/src/exercice2/elided1.rs b/tp2/exercice2/elided1.rs similarity index 100% rename from tp2/src/exercice2/elided1.rs rename to tp2/exercice2/elided1.rs diff --git a/tp2/src/exercice2/elided2.rs b/tp2/exercice2/elided2.rs similarity index 100% rename from tp2/src/exercice2/elided2.rs rename to tp2/exercice2/elided2.rs diff --git a/tp2/src/exercice2/elided3.rs b/tp2/exercice2/elided3.rs similarity index 100% rename from tp2/src/exercice2/elided3.rs rename to tp2/exercice2/elided3.rs diff --git a/tp2/src/exercice2/explicit1.rs b/tp2/exercice2/explicit1.rs similarity index 100% rename from tp2/src/exercice2/explicit1.rs rename to tp2/exercice2/explicit1.rs diff --git a/tp2/src/exercice2/explicit2.rs b/tp2/exercice2/explicit2.rs similarity index 100% rename from tp2/src/exercice2/explicit2.rs rename to tp2/exercice2/explicit2.rs diff --git a/tp2/src/exercice2/explicit3.rs b/tp2/exercice2/explicit3.rs similarity index 100% rename from tp2/src/exercice2/explicit3.rs rename to tp2/exercice2/explicit3.rs diff --git a/tp2/src/exercice2/types1.rs b/tp2/exercice2/types1.rs similarity index 100% rename from tp2/src/exercice2/types1.rs rename to tp2/exercice2/types1.rs diff --git a/tp2/src/exercice2/types2.rs b/tp2/exercice2/types2.rs similarity index 100% rename from tp2/src/exercice2/types2.rs rename to tp2/exercice2/types2.rs diff --git a/tp2/Cargo.lock b/tp2/exercice3/Cargo.lock similarity index 87% rename from tp2/Cargo.lock rename to tp2/exercice3/Cargo.lock index d2be7f1af251dfad3e1754e1cc6a6408e44b4dbb..b8488135d8e4b354f45f7970678b400e9fd8cb03 100644 --- a/tp2/Cargo.lock +++ b/tp2/exercice3/Cargo.lock @@ -3,5 +3,5 @@ version = 4 [[package]] -name = "tp2" +name = "exercice3" version = "0.1.0" diff --git a/tp2/Cargo.toml b/tp2/exercice3/Cargo.toml similarity index 76% rename from tp2/Cargo.toml rename to tp2/exercice3/Cargo.toml index 1d772e49be8655949d374be421f1944c49876ff0..ca2a730e47287a36a59382541b52b92767e8312d 100644 --- a/tp2/Cargo.toml +++ b/tp2/exercice3/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "tp2" +name = "exercice3" version = "0.1.0" edition = "2024" diff --git a/tp2/exercice3/src/main.rs b/tp2/exercice3/src/main.rs new file mode 100644 index 0000000000000000000000000000000000000000..449efd235d09e28262dbab8c6c90fd62b4849760 --- /dev/null +++ b/tp2/exercice3/src/main.rs @@ -0,0 +1,104 @@ +use std::net::{TcpListener, TcpStream}; +use std::io::{BufReader, BufRead, Write, Read}; +use std::fs; +use std::path::Path; + +fn main() { + // Le serveur écoute sur localhost:8080 + let listener = TcpListener::bind("127.0.0.1:8080").unwrap(); + println!("Server listening on http://127.0.0.1:8080"); + + for stream in listener.incoming() { + match stream { + Ok(stream) => { + // Pour chaque connexion, on lance un thread pour la traiter + std::thread::spawn(|| { + handle_connection(stream); + }); + }, + Err(e) => eprintln!("Failed to establish connection: {}", e), + } + } +} + +fn handle_connection(mut stream: TcpStream) { + let mut reader = BufReader::new(&stream); + let mut request_line = String::new(); + + // Lecture de la première ligne de la requête (ex: "GET / HTTP/1.1") + if reader.read_line(&mut request_line).is_err() { + return; + } + let request_line = request_line.trim_end(); + + // Découpage de la ligne de requête + let parts: Vec<&str> = request_line.split_whitespace().collect(); + if parts.len() < 3 { + send_response(&mut stream, "HTTP/1.1 400 Bad Request", "text/plain", b"Bad Request"); + return; + } + let method = parts[0]; + let path = parts[1]; + let _version = parts[2]; + + // Seul le GET est supporté + if method != "GET" { + send_response(&mut stream, "HTTP/1.1 405 Method Not Allowed", "text/plain", b"Method Not Allowed"); + return; + } + + // On lit et jette les autres lignes d'en-tête jusqu'à une ligne vide + for line in reader.by_ref().lines() { + match line { + Ok(line) if line.trim().is_empty() => break, + Ok(_) => continue, + Err(_) => break, + } + } + + // Route par défaut pour "/" + if path == "/" { + let body = b"Hello world"; + send_response(&mut stream, "HTTP/1.1 200 OK", "text/plain", body); + return; + } + + // Construction du chemin du fichier demandé + // On enlève le '/' initial pour obtenir un chemin relatif + let file_path = &path[1..]; + match fs::read(file_path) { + Ok(contents) => { + // Détermination du Content-type selon l'extension du fichier + let content_type = match Path::new(file_path).extension().and_then(|ext| ext.to_str()) { + Some("html") => "text/html", + Some("css") => "text/css", + Some("js") => "application/javascript", + Some("jpg") => "image/jpeg", + Some("png") => "image/png", + Some("svg") => "image/svg+xml", + Some("woff") => "font/woff", + Some("woff2")=> "font/woff2", + _ => "text/plain", + }; + send_response(&mut stream, "HTTP/1.1 200 OK", content_type, &contents); + }, + Err(ref e) if e.kind() == std::io::ErrorKind::NotFound => { + send_response(&mut stream, "HTTP/1.1 404 Not Found", "text/plain", b"Not Found"); + }, + Err(_) => { + send_response(&mut stream, "HTTP/1.1 500 Internal Server Error", "text/plain", b"Internal Server Error"); + } + } +} + +fn send_response(stream: &mut TcpStream, status_line: &str, content_type: &str, body: &[u8]) { + // Construction de la réponse HTTP avec les en-têtes requis + let response = format!( + "{}\r\nContent-type: {}\r\nContent-length: {}\r\n\r\n", + status_line, + content_type, + body.len() + ); + let _ = stream.write_all(response.as_bytes()); + let _ = stream.write_all(body); +}