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