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}