feather_runtime/http/
response.rs

1use super::errors::HeaderError;
2use bytes::{Bytes, BytesMut};
3use http::{HeaderMap, HeaderName, HeaderValue, StatusCode};
4#[cfg(feature = "json")]
5use serde::Serialize;
6use std::{fs::File, io::Read, str::FromStr};
7
8#[derive(Debug, Default)]
9pub struct Response {
10    /// The HTTP status code of the response.
11    /// This is a 3-digit integer that indicates the result of the request.
12    pub status: StatusCode,
13    /// The headers of the HTTP response.
14    /// Headers are key-value pairs that provide additional information about the response.
15    pub headers: HeaderMap,
16    /// The body of the HTTP response.
17    /// This is the content that is sent back to the client.
18    /// The body is represented as a `Bytes` object for efficient handling of binary data.
19    pub body: Option<Bytes>,
20    /// The HTTP version of the response.
21    pub version: http::Version,
22}
23
24impl Response {
25    const MAX_FILE_SIZE_BYTES: u64 = 4 * 1024 * 1024; // 4 MB
26
27    /// Internal helper to set common headers
28    fn set_common_headers(&mut self, content_type: Option<&'static str>, len: usize) {
29        if let Some(ct) = content_type {
30            self.headers.insert(HeaderName::from_static("content-type"), HeaderValue::from_static(ct));
31        }
32        self.headers.insert(HeaderName::from_static("content-length"), Self::len_to_header_value(len));
33    }
34
35    /// Sets the StatusCode of the response and Returns a Muteable Reference to the Response
36    /// ```rust,ignore
37    /// res.status(200).send_text("hello");
38    /// ```
39    pub fn set_status(&mut self, status: u16) -> &mut Response {
40        self.status = StatusCode::from_u16(status).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
41        self
42    }
43
44    /// Adds a header to the response.
45    /// The header is a key-value pair that provides additional information about the response.
46    ///
47    pub fn add_header(&mut self, key: &str, value: &str) -> Result<(), HeaderError> {
48        let val = HeaderValue::from_str(value)?;
49        let key = HeaderName::from_str(key)?;
50        self.headers.insert(key, val);
51        return Ok(());
52    }
53    /// Converts the `Response` into a raw HTTP response as Bytes.
54    pub fn to_raw(&self) -> Bytes {
55        let body_len = self.body.as_ref().map_or(0, |b| b.len());
56        // Start buffer with a reasonable capacity to avoid reallocations.
57        let mut buf = BytesMut::with_capacity(512 + body_len);
58
59        // --- 1. Status Line (HTTP/1.1 200 OK\r\n) ---
60        buf.extend_from_slice(b"HTTP/1.1 ");
61
62        // Use itoa::Buffer for stack-allocated status code formatting
63        let mut status_buffer = itoa::Buffer::new();
64        let status_code_str = status_buffer.format(self.status.as_u16());
65
66        buf.extend_from_slice(status_code_str.as_bytes());
67        buf.extend_from_slice(b" ");
68
69        // Canonical Reason (e.g., "OK", "Not Found")
70        buf.extend_from_slice(self.status.canonical_reason().unwrap_or("Unknown").as_bytes());
71        buf.extend_from_slice(b"\r\n");
72
73        // --- 2. Existing Headers ---
74        for (key, value) in &self.headers {
75            // Header Name
76            buf.extend_from_slice(key.as_str().as_bytes());
77            buf.extend_from_slice(b": ");
78            // Header Value (already HeaderValue::from_static or from_str)
79            buf.extend_from_slice(value.as_bytes());
80            buf.extend_from_slice(b"\r\n");
81        }
82
83        // --- 3. Date Header Insertion (Crucial for HTTP/1.1) ---
84        // Insert Date header if the user hasn't explicitly set it.
85        // NOTE: This still uses a string allocation via `to_rfc2822()`.
86        // For the absolute fastest approach, this string would be cached system-wide
87        // and updated every second.
88        if !self.headers.contains_key("date") {
89            let date_str = chrono::Utc::now().to_rfc2822();
90            buf.extend_from_slice(b"date: ");
91            buf.extend_from_slice(date_str.as_bytes());
92            buf.extend_from_slice(b"\r\n");
93        }
94
95        // --- 4. Content-Length Header Insertion ---
96        // Insert Content-Length if it's not set AND there is a body.
97        if !self.headers.contains_key("content-length") && body_len > 0 {
98            buf.extend_from_slice(b"content-length: ");
99
100            // Use itoa::Buffer for stack-allocated length formatting
101            let mut len_buffer = itoa::Buffer::new();
102            let len_str = len_buffer.format(body_len);
103
104            buf.extend_from_slice(len_str.as_bytes());
105            buf.extend_from_slice(b"\r\n");
106        }
107
108        // --- 5. Header/Body Separator ---
109        buf.extend_from_slice(b"\r\n");
110
111        // --- 6. Body ---
112        if let Some(ref body) = self.body {
113            buf.extend_from_slice(body);
114        }
115
116        // Convert mutable buffer to immutable Bytes type
117        buf.freeze()
118    }
119
120    /// Sends given String as given text
121    pub fn send_text(&mut self, data: impl Into<String>) {
122        let body = data.into();
123        self.body = Some(Bytes::from(body));
124        self.set_common_headers(Some("text/plain;charset=utf-8"), self.body.as_ref().unwrap().len());
125    }
126
127    /// Sends Given Bytes as plain text
128    pub fn send_bytes(&mut self, data: impl Into<Vec<u8>>) {
129        let body = data.into();
130        self.body = Some(Bytes::from(body));
131        self.set_common_headers(None, self.body.as_ref().unwrap().len());
132    }
133
134    ///Takes a String(Should be valid HTML) and sends it's as Html
135    pub fn send_html(&mut self, data: impl Into<String>) {
136        let body = data.into();
137        self.body = Some(Bytes::from(body));
138        self.headers.insert(HeaderName::from_static("content-type"), HeaderValue::from_static("text/html"));
139        let len = self.body.as_ref().unwrap().len();
140        self.headers.insert(HeaderName::from_static("content-length"), Self::len_to_header_value(len));
141    }
142
143    /// Takes a Serializeable object and sends it as json.
144    #[cfg(feature = "json")]
145    pub fn send_json<T: Serialize>(&mut self, data: T) {
146        match serde_json::to_string(&data) {
147            Ok(json) => {
148                self.body = Some(Bytes::from(json));
149                self.headers.insert(HeaderName::from_static("content-type"), HeaderValue::from_static("application/json"));
150                let len = self.body.as_ref().unwrap().len();
151                self.headers.insert(HeaderName::from_static("content-length"), Self::len_to_header_value(len));
152            }
153            Err(_) => {
154                self.status = StatusCode::INTERNAL_SERVER_ERROR;
155                self.body = Some(Bytes::from("Internal Server Error"));
156                self.headers.insert(HeaderName::from_static("content-type"), HeaderValue::from_static("text/plain"));
157                let len = self.body.as_ref().unwrap().len();
158                self.headers.insert(HeaderName::from_static("content-length"), Self::len_to_header_value(len));
159            }
160        }
161    }
162    /// Take a [File] Struct and sends it as a file.
163    /// File size is limited to 4MB. For larger files, chunked transfer\[WIP] is recommended.
164    pub fn send_file(&mut self, mut file: File) {
165        let metadata = match file.metadata() {
166            Ok(m) => m,
167            Err(_) => {
168                self.status = StatusCode::INTERNAL_SERVER_ERROR;
169                self.body = Some(Bytes::from("Failed to read file metadata."));
170                return;
171            }
172        };
173
174        // ENFORCE LIMIT: 4MB
175        if metadata.len() > Self::MAX_FILE_SIZE_BYTES {
176            self.status = StatusCode::PAYLOAD_TOO_LARGE; // 413
177            self.body = Some(Bytes::from("File size exceeds 4MB limit. Use chunked encoding for larger files."));
178            return;
179        }
180
181        let mut buffer = Vec::new();
182        match file.read_to_end(&mut buffer) {
183            Ok(_) => {
184                self.body = Some(Bytes::from(buffer));
185                let len = self.body.as_ref().unwrap().len();
186                self.headers.insert(HeaderName::from_static("content-length"), Self::len_to_header_value(len));
187                // ? NOTE: Consider adding feature : Content-Type based on file extension
188            }
189            Err(_) => {
190                self.status = StatusCode::INTERNAL_SERVER_ERROR;
191                self.body = Some(Bytes::from("Internal Server Error during file read."));
192            }
193        }
194    }
195
196    pub fn redirect(&mut self, location: &str, permanent: bool) {
197        let status = if permanent {
198            StatusCode::MOVED_PERMANENTLY
199        } else {
200            StatusCode::FOUND
201        };
202        self.set_status(status.as_u16());
203        self.headers.insert(HeaderName::from_static("location"), HeaderValue::from_str(location).unwrap());
204        self.body = Some(Bytes::from(format!("Redirecting to {}", location)));
205        let len = self.body.as_ref().unwrap().len();
206        self.headers.insert(HeaderName::from_static("content-length"), Self::len_to_header_value(len));
207    }
208
209    /// A Utily Function for wrapping HeaderValue for Content-Lenght
210    fn len_to_header_value(len: usize) -> HeaderValue {
211        let mut buffer = itoa::Buffer::new();
212        let len_str = buffer.format(len);
213
214        // ! SAFETY: Content-Length is only ASCII digits, which is safe for HeaderValue::from_bytes
215        HeaderValue::from_bytes(len_str.as_bytes()).expect("itoa::Buffer output should be a valid HeaderValue")
216    }
217}