webserver_colin_ugo/http/
response.rs

1use std::io::{Result, Write};
2use crate::http::StatusCode;
3use crate::utils::{get_http_date, get_server_version};
4
5/// Envoie une réponse HTTP GET au client.
6///
7/// # Arguments
8///
9/// * `stream` - Un objet implémentant Write (comme TcpStream)
10/// * `status_line` - La ligne de statut HTTP (ex: "HTTP/1.1 200 OK").
11/// * `content_type` - Le type de contenu de la réponse (ex: "text/html").
12/// * `contents` - Le contenu de la réponse (ex: le corps HTML).
13/// * `connection` - La valeur de l'en-tête "Connection" de la requête.
14///
15/// # Retourne
16///
17/// * `Result<()>` - Un résultat indiquant si l'envoi a réussi ou échoué.
18pub fn send_get_response<W: Write>(
19    stream: &mut W,
20    status_line: &str,
21    content_type: &str,
22    contents: String,
23    connection: &str,
24) -> Result<()> {
25    let length = contents.len();
26    let date = get_http_date();
27    let server = get_server_version();
28
29    let response = format!(
30        "{}\r\nServer: {}\r\nDate: {}\r\nContent-Length: {}\r\nContent-Type: {}\r\nConnection: {}\r\n\r\n{}",
31        status_line, server, date, length, content_type, connection, contents
32    );
33
34    stream.write_all(response.as_bytes())
35}
36
37/// Envoie une réponse HTTP GET avec du contenu binaire au client.
38///
39/// # Arguments
40///
41/// * `stream` - Un objet implémentant Write (comme TcpStream)
42/// * `status_line` - La ligne de statut HTTP (ex: "HTTP/1.1 200 OK").
43/// * `content_type` - Le type de contenu de la réponse (ex: "image/jpeg").
44/// * `contents` - Le contenu binaire de la réponse.
45/// * `connection` - La valeur de l'en-tête "Connection" de la requête.
46///
47/// # Retourne
48///
49/// * `Result<()>` - Un résultat indiquant si l'envoi a réussi ou échoué.
50pub fn send_binary_response<W: Write>(
51    stream: &mut W,
52    status_line: &str,
53    content_type: &str,
54    contents: &[u8],
55    connection: &str,
56) -> Result<()> {
57    let length = contents.len();
58    let date = get_http_date();
59    let server = get_server_version();
60
61    let header = format!(
62        "{}\r\nServer: {}\r\nDate: {}\r\nContent-Length: {}\r\nContent-Type: {}\r\nConnection: {}\r\n\r\n",
63        status_line, server, date, length, content_type, connection
64    );
65    
66    stream.write_all(header.as_bytes())?;
67    
68    stream.write_all(contents)
69}
70
71/// Envoie une réponse HTTP HEAD au client.
72///
73/// # Arguments
74///
75/// * `stream` - Un objet implémentant Write (comme TcpStream)
76/// * `status_line` - La ligne de statut HTTP (ex: "HTTP/1.1 200 OK").
77/// * `content_type` - Le type de contenu de la réponse (ex: "text/html").
78/// * `connection` - La valeur de l'en-tête "Connection" de la requête.
79///
80/// # Retourne
81///
82/// * `Result<()>` - Un résultat indiquant si l'envoi a réussi ou échoué.
83pub fn send_head_response<W: Write>(
84    stream: &mut W,
85    status_line: &str,
86    content_type: &str,
87    connection: &str,
88) -> Result<()> {
89    let date = get_http_date();
90    let server = get_server_version();
91    
92    let response = format!(
93        "{}\r\nServer: {}\r\nDate: {}\r\nContent-Length: 0\r\nContent-Type: {}\r\nConnection: {}\r\n\r\n",
94        status_line, server, date, content_type, connection
95    );
96
97    stream.write_all(response.as_bytes())
98}
99
100/// Envoie une réponse HTTP 400 Bad Request au client.
101///
102/// # Arguments
103///
104/// * `stream` - Un objet implémentant Write (comme TcpStream)
105/// * `connection` - La valeur de l'en-tête "Connection" de la requête.
106///
107/// # Retourne
108///
109/// * `Result<()>` - Un résultat indiquant si l'envoi a réussi ou échoué.
110pub fn send_bad_request_response<W: Write>(
111    stream: &mut W,
112    connection: &str,
113) -> Result<()> {
114    let contents = "<h1>400 Bad Request</h1>".to_string();
115    let length = contents.len();
116    let date = get_http_date();
117    let server = get_server_version();
118
119    let response = format!(
120        "{}\r\nServer: {}\r\nDate: {}\r\nContent-Length: {}\r\nContent-Type: text/html\r\nConnection: {}\r\n\r\n{}",
121        StatusCode::BAD_REQUEST, server, date, length, connection, contents
122    );
123
124    stream.write_all(response.as_bytes())
125}
126
127/// Envoie une réponse HTTP 403 Forbidden au client.
128///
129/// # Arguments
130///
131/// * `stream` - Un objet implémentant Write (comme TcpStream)
132/// * `connection` - La valeur de l'en-tête "Connection" de la requête.
133///
134/// # Retourne
135///
136/// * `Result<()>` - Un résultat indiquant si l'envoi a réussi ou échoué.
137pub fn send_forbidden_response<W: Write>(
138    stream: &mut W,
139    connection: &str,
140) -> Result<()> {
141    let contents = "<h1>403 Forbidden</h1>".to_string();
142    let length = contents.len();
143    let date = get_http_date();
144    let server = get_server_version();
145
146    let response = format!(
147        "{}\r\nServer: {}\r\nDate: {}\r\nContent-Length: {}\r\nContent-Type: text/html\r\nConnection: {}\r\n\r\n{}",
148        StatusCode::FORBIDDEN, server, date, length, connection, contents
149    );
150
151    stream.write_all(response.as_bytes())
152}
153
154/// Envoie une réponse HTTP 404 Not Found au client.
155///
156/// # Arguments
157///
158/// * `stream` - Un objet implémentant Write (comme TcpStream)
159/// * `connection` - La valeur de l'en-tête "Connection" de la requête.
160///
161/// # Retourne
162///
163/// * `Result<()>` - Un résultat indiquant si l'envoi a réussi ou échoué.
164pub fn send_not_found_response<W: Write>(
165    stream: &mut W,
166    connection: &str,
167) -> Result<()> {
168    let contents = "<h1>404 Not Found</h1>".to_string();
169    let length = contents.len();
170    let date = get_http_date();
171    let server = get_server_version();
172
173    let response = format!(
174        "{}\r\nServer: {}\r\nDate: {}\r\nContent-Length: {}\r\nContent-Type: text/html\r\nConnection: {}\r\n\r\n{}",
175        StatusCode::NOT_FOUND, server, date, length, connection, contents
176    );
177
178    stream.write_all(response.as_bytes())
179}
180
181/// Envoie une réponse HTTP 405 Method Not Allowed au client.
182///
183/// # Arguments
184///
185/// * `stream` - Un objet implémentant Write (comme TcpStream)
186/// * `connection` - La valeur de l'en-tête "Connection" de la requête.
187///
188/// # Retourne
189///
190/// * `Result<()>` - Un résultat indiquant si l'envoi a réussi ou échoué.
191pub fn send_method_not_allowed_response<W: Write>(
192    stream: &mut W,
193    connection: &str,
194) -> Result<()> {
195    let contents = "<h1>405 Method Not Allowed</h1>".to_string();
196    let length = contents.len();
197    let date = get_http_date();
198    let server = get_server_version();
199
200    let response = format!(
201        "{}\r\nServer: {}\r\nDate: {}\r\nContent-Length: {}\r\nContent-Type: text/html\r\nConnection: {}\r\n\r\n{}",
202        StatusCode::METHOD_NOT_ALLOWED, server, date, length, connection, contents
203    );
204
205    stream.write_all(response.as_bytes())
206}
207
208/// Envoie une réponse HTTP 500 Internal Server Error au client.
209///
210/// # Arguments
211///
212/// * `stream` - Un objet implémentant Write (comme TcpStream)
213/// * `connection` - La valeur de l'en-tête "Connection" de la requête.
214///
215/// # Retourne
216///
217/// * `Result<()>` - Un résultat indiquant si l'envoi a réussi ou échoué.
218pub fn send_internal_server_error_response<W: Write>(
219    stream: &mut W,
220    connection: &str,
221) -> Result<()> {
222    let contents = "<h1>500 Internal Server Error</h1>".to_string();
223    let length = contents.len();
224    let date = get_http_date();
225    let server = get_server_version();
226
227    let response = format!(
228        "{}\r\nServer: {}\r\nDate: {}\r\nContent-Length: {}\r\nContent-Type: text/html\r\nConnection: {}\r\n\r\n{}",
229        StatusCode::INTERNAL_SERVER_ERROR, server, date, length, connection, contents
230    );
231
232    stream.write_all(response.as_bytes())
233}
234
235/// Envoie une réponse HTTP 501 Not Implemented au client.
236///
237/// # Arguments
238///
239/// * `stream` - Un objet implémentant Write (comme TcpStream)
240/// * `connection` - La valeur de l'en-tête "Connection" de la requête.
241///
242/// # Retourne
243///
244/// * `Result<()>` - Un résultat indiquant si l'envoi a réussi ou échoué.
245pub fn send_not_implemented_response<W: Write>(
246    stream: &mut W,
247    connection: &str,
248) -> Result<()> {
249    let contents = "<h1>501 Not Implemented</h1>".to_string();
250    let length = contents.len();
251    let date = get_http_date();
252    let server = get_server_version();
253
254    let response = format!(
255        "{}\r\nServer: {}\r\nDate: {}\r\nContent-Length: {}\r\nContent-Type: text/html\r\nConnection: {}\r\n\r\n{}",
256        StatusCode::NOT_IMPLEMENTED, server, date, length, connection, contents
257    );
258
259    stream.write_all(response.as_bytes())
260}
261
262/// Envoie une réponse HTTP 505 HTTP Version Not Supported au client.
263///
264/// # Arguments
265///
266/// * `stream` - Un objet implémentant Write (comme TcpStream)
267/// * `connection` - La valeur de l'en-tête "Connection" de la requête.
268///
269/// # Retourne
270///
271/// * `Result<()>` - Un résultat indiquant si l'envoi a réussi ou échoué.
272pub fn send_http_version_not_supported_response<W: Write>(
273    stream: &mut W,
274    connection: &str,
275) -> Result<()> {
276    let contents = "<h1>505 HTTP Version Not Supported</h1>".to_string();
277    let length = contents.len();
278    let date = get_http_date();
279    let server = get_server_version();
280
281    let response = format!(
282        "{}\r\nServer: {}\r\nDate: {}\r\nContent-Length: {}\r\nContent-Type: text/html\r\nConnection: {}\r\n\r\n{}",
283        StatusCode::HTTP_VERSION_NOT_SUPPORTED, server, date, length, connection, contents
284    );
285
286    stream.write_all(response.as_bytes())
287}
288
289#[cfg(test)]
290mod tests {
291    use super::*;
292    use std::io::{Cursor, Read};
293
294    fn read_response(cursor: &mut Cursor<Vec<u8>>) -> String {
295        let mut content = String::new();
296        cursor.set_position(0);
297        cursor.read_to_string(&mut content).unwrap();
298        content
299    }
300
301    #[test]
302    fn test_send_get_response() {
303        let mut cursor = Cursor::new(vec![]);
304        let test_content = "<h1>Hello</h1>".to_string();
305        let content_length = test_content.len().to_string();
306        
307        send_get_response(
308            &mut cursor,
309            StatusCode::OK,
310            "text/html",
311            test_content,
312            "close",
313        )
314        .unwrap();
315
316        let response = read_response(&mut cursor);
317        assert!(response.contains("HTTP/1.1 200 OK"));
318        assert!(response.contains("Content-Type: text/html"));
319        assert!(response.contains(&format!("Content-Length: {}", content_length)));
320        assert!(response.contains("Connection: close"));
321        assert!(response.contains("<h1>Hello</h1>"));
322        assert!(response.contains("Server: Rust-WebServer"));
323        assert!(response.contains("Date: "));
324    }
325
326    #[test]
327    fn test_send_head_response() {
328        let mut cursor = Cursor::new(vec![]);
329        send_head_response(&mut cursor, StatusCode::OK, "text/html", "close").unwrap();
330
331        let response = read_response(&mut cursor);
332        assert!(response.contains("HTTP/1.1 200 OK"));
333        assert!(response.contains("Content-Type: text/html"));
334        assert!(response.contains("Content-Length: 0"));
335        assert!(response.contains("Connection: close"));
336        assert!(response.contains("Server: Rust-WebServer"));
337        assert!(response.contains("Date: "));
338        assert!(!response.contains("<h1>"));
339    }
340
341    #[test]
342    fn test_error_responses() {
343        // Teste chaque fonction de réponse d'erreur individuellement
344        let mut cursor = Cursor::new(vec![]);
345        send_not_found_response(&mut cursor, "close").unwrap();
346        let response = read_response(&mut cursor);
347        assert!(response.contains("404"));
348        assert!(response.contains("Server: Rust-WebServer"));
349        assert!(response.contains("Date: "));
350        
351        let mut cursor = Cursor::new(vec![]);
352        send_forbidden_response(&mut cursor, "close").unwrap();
353        let response = read_response(&mut cursor);
354        assert!(response.contains("403"));
355        assert!(response.contains("Server: Rust-WebServer"));
356        
357        let mut cursor = Cursor::new(vec![]);
358        send_method_not_allowed_response(&mut cursor, "close").unwrap();
359        let response = read_response(&mut cursor);
360        assert!(response.contains("405"));
361        
362        let mut cursor = Cursor::new(vec![]);
363        send_internal_server_error_response(&mut cursor, "close").unwrap();
364        let response = read_response(&mut cursor);
365        assert!(response.contains("500"));
366        
367        let mut cursor = Cursor::new(vec![]);
368        send_not_implemented_response(&mut cursor, "close").unwrap();
369        let response = read_response(&mut cursor);
370        assert!(response.contains("501"));
371        
372        let mut cursor = Cursor::new(vec![]);
373        send_http_version_not_supported_response(&mut cursor, "close").unwrap();
374        let response = read_response(&mut cursor);
375        assert!(response.contains("505"));
376    }
377}