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);
+}