http_handle/
response.rs

1// src/response.rs
2
3//! HTTP Response module for handling and sending server responses.
4//!
5//! This module defines the `Response` struct, which represents an HTTP response
6//! sent from the server to a client. The `Response` struct includes the status code,
7//! status text, headers, and body of the response. It provides functionality for creating
8//! and sending HTTP responses over a stream that implements the `Write` trait.
9//!
10//! The main components and methods of this module include:
11//!
12//! - `Response`: Represents an HTTP response, containing status code, headers, and body.
13//! - `Response::new`: Creates a new `Response` instance.
14//! - `Response::add_header`: Adds custom headers to the response.
15//! - `Response::send`: Sends the response over a writable stream (e.g., a network socket).
16//!
17//! This module integrates error handling via the `ServerError` type, ensuring that issues
18//! with sending the response or writing to the stream are properly captured and handled.
19//!
20//! # Example
21//!
22//! ```rust
23//! use http_handle::response::Response;
24//! use std::io::Cursor;
25//!
26//! let mut response = Response::new(200, "OK", b"Hello, world!".to_vec());
27//! response.add_header("Content-Type", "text/plain");
28//!
29//! let mut mock_stream = Cursor::new(Vec::new());
30//! response.send(&mut mock_stream).unwrap();
31//!
32//! let written_data = mock_stream.into_inner();
33//! assert!(written_data.starts_with(b"HTTP/1.1 200 OK\r\n"));
34//! ```
35//!
36//! This module is responsible for creating and formatting the HTTP status line, headers,
37//! and body before sending it to the client over a stream, ensuring compliance with the
38//! HTTP/1.1 protocol.
39
40use crate::error::ServerError;
41use serde::{Deserialize, Serialize};
42use std::io::Write;
43
44/// Represents an HTTP response, including the status code, status text, headers, and body.
45#[derive(
46    Clone, Debug, PartialEq, Eq, Hash, Default, Serialize, Deserialize,
47)]
48pub struct Response {
49    /// The HTTP status code (e.g., 200 for OK, 404 for Not Found).
50    pub status_code: u16,
51
52    /// The HTTP status text associated with the status code (e.g., "OK", "Not Found").
53    pub status_text: String,
54
55    /// A list of headers in the response, each represented as a tuple containing the header
56    /// name and its corresponding value.
57    pub headers: Vec<(String, String)>,
58
59    /// The body of the response, represented as a vector of bytes.
60    pub body: Vec<u8>,
61}
62
63impl Response {
64    /// Creates a new `Response` with the given status code, status text, and body.
65    ///
66    /// The headers are initialized as an empty list and can be added later using the `add_header` method.
67    ///
68    /// # Arguments
69    ///
70    /// * `status_code` - The HTTP status code for the response.
71    /// * `status_text` - The status text corresponding to the status code.
72    /// * `body` - The body of the response, represented as a vector of bytes.
73    ///
74    /// # Returns
75    ///
76    /// A new `Response` instance with the specified status code, status text, and body.
77    pub fn new(
78        status_code: u16,
79        status_text: &str,
80        body: Vec<u8>,
81    ) -> Self {
82        Response {
83            status_code,
84            status_text: status_text.to_string(),
85            headers: Vec::new(),
86            body,
87        }
88    }
89
90    /// Adds a header to the response.
91    ///
92    /// This method allows you to add custom headers to the response, which will be included
93    /// in the HTTP response when it is sent to the client.
94    ///
95    /// # Arguments
96    ///
97    /// * `name` - The name of the header (e.g., "Content-Type").
98    /// * `value` - The value of the header (e.g., "text/html").
99    pub fn add_header(&mut self, name: &str, value: &str) {
100        self.headers.push((name.to_string(), value.to_string()));
101    }
102
103    /// Sends the response over the provided `Write` stream.
104    ///
105    /// This method writes the HTTP status line, headers, and body to the stream, ensuring
106    /// the client receives the complete response.
107    ///
108    /// # Arguments
109    ///
110    /// * `stream` - A mutable reference to any stream that implements `Write`.
111    ///
112    /// # Returns
113    ///
114    /// * `Ok(())` - If the response is successfully sent.
115    /// * `Err(ServerError)` - If an error occurs while sending the response.
116    pub fn send<W: Write>(
117        &self,
118        stream: &mut W,
119    ) -> Result<(), ServerError> {
120        write!(
121            stream,
122            "HTTP/1.1 {} {}\r\n",
123            self.status_code, self.status_text
124        )?;
125
126        for (name, value) in &self.headers {
127            write!(stream, "{}: {}\r\n", name, value)?;
128        }
129
130        write!(stream, "\r\n")?;
131        stream.write_all(&self.body)?;
132        stream.flush()?;
133
134        Ok(())
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141    use std::io::{self, Cursor, Write};
142
143    /// Test case for the `Response::new` method.
144    #[test]
145    fn test_response_new() {
146        let status_code = 200;
147        let status_text = "OK";
148        let body = b"Hello, world!".to_vec();
149        let response =
150            Response::new(status_code, status_text, body.clone());
151
152        assert_eq!(response.status_code, status_code);
153        assert_eq!(response.status_text, status_text.to_string());
154        assert!(response.headers.is_empty());
155        assert_eq!(response.body, body);
156    }
157
158    /// Test case for the `Response::add_header` method.
159    #[test]
160    fn test_response_add_header() {
161        let mut response = Response::new(200, "OK", vec![]);
162        response.add_header("Content-Type", "text/html");
163
164        assert_eq!(response.headers.len(), 1);
165        assert_eq!(
166            response.headers[0],
167            ("Content-Type".to_string(), "text/html".to_string())
168        );
169    }
170
171    /// A mock implementation of `Write` to simulate writing the response without actual network operations.
172    struct MockTcpStream {
173        buffer: Cursor<Vec<u8>>,
174    }
175
176    impl MockTcpStream {
177        fn new() -> Self {
178            MockTcpStream {
179                buffer: Cursor::new(Vec::new()),
180            }
181        }
182
183        fn get_written_data(&self) -> Vec<u8> {
184            self.buffer.clone().into_inner()
185        }
186    }
187
188    impl Write for MockTcpStream {
189        fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
190            self.buffer.write(buf)
191        }
192
193        fn flush(&mut self) -> io::Result<()> {
194            self.buffer.flush()
195        }
196    }
197
198    /// Test case for the `Response::send` method.
199    #[test]
200    fn test_response_send() {
201        let mut response =
202            Response::new(200, "OK", b"Hello, world!".to_vec());
203        response.add_header("Content-Type", "text/plain");
204
205        let mut mock_stream = MockTcpStream::new();
206        let result = response.send(&mut mock_stream);
207
208        assert!(result.is_ok());
209
210        let expected_output = b"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nHello, world!";
211        let written_data = mock_stream.get_written_data();
212
213        assert_eq!(written_data, expected_output);
214    }
215
216    /// Test case for `Response::send` when there is an error during writing.
217    #[test]
218    fn test_response_send_error() {
219        let mut response =
220            Response::new(200, "OK", b"Hello, world!".to_vec());
221        response.add_header("Content-Type", "text/plain");
222
223        struct FailingStream;
224
225        impl Write for FailingStream {
226            fn write(&mut self, _buf: &[u8]) -> io::Result<usize> {
227                Err(io::Error::new(io::ErrorKind::Other, "write error"))
228            }
229
230            fn flush(&mut self) -> io::Result<()> {
231                Ok(())
232            }
233        }
234
235        let mut failing_stream = FailingStream;
236        let result = response.send(&mut failing_stream);
237
238        assert!(result.is_err());
239    }
240}