Skip to main content

http_request/response/
impl.rs

1use super::*;
2
3impl Default for HttpResponse {
4    /// Returns the default `HttpResponse`.
5    ///
6    /// The status code and reason phrase fall back to `HttpStatus::Unknown`,
7    /// the header map is empty, and the body is empty.
8    fn default() -> Self {
9        Self {
10            version: HttpVersion::default(),
11            status_code: HttpStatus::Unknown.code(),
12            reason_phrase: HttpStatus::Unknown.to_string(),
13            headers: new_response_headers(),
14            body: ResponseBody::new(),
15        }
16    }
17}
18
19impl HttpResponse {
20    /// Construct from raw response bytes (status line + headers + body).
21    ///
22    /// # Arguments
23    ///
24    /// - `&[u8]` - The complete raw response bytes, including the status line,
25    ///   the header block, and the body.
26    pub fn from_bytes(response: &[u8]) -> Self {
27        let split_lines: Vec<&[u8]> = split_multi_byte(response, HTTP_BR_BYTES);
28        let mut lines: IntoIter<&[u8]> = split_lines.into_iter();
29        let status_line: &[u8] = lines.next().unwrap_or(&[]);
30        let status_parts: Vec<&[u8]> = split_whitespace(status_line);
31        let version: HttpVersion = status_parts
32            .first()
33            .and_then(|part: &&[u8]| from_utf8(part).ok())
34            .and_then(|version_str: &str| version_str.parse::<HttpVersion>().ok())
35            .unwrap_or_default();
36        let status_code: ResponseStatusCode = status_parts
37            .get(1)
38            .and_then(|part: &&[u8]| from_utf8(part).ok())
39            .and_then(|code_str: &str| code_str.parse().ok())
40            .unwrap_or(HttpStatus::Unknown.code());
41        let reason_phrase: String = status_parts.get(2..).map_or_else(
42            || HttpStatus::Unknown.to_string(),
43            |parts: &[&[u8]]| {
44                if parts.is_empty() {
45                    HttpStatus::Unknown.to_string()
46                } else if parts.len() == 1 {
47                    String::from_utf8_lossy(parts[0]).into_owned()
48                } else {
49                    let total_len: usize =
50                        parts.iter().map(|p: &&[u8]| p.len()).sum::<usize>() + parts.len() - 1;
51                    let mut result: String = String::with_capacity(total_len);
52                    for (i, part) in parts.iter().enumerate() {
53                        if i > 0 {
54                            result.push(' ');
55                        }
56                        result.push_str(&String::from_utf8_lossy(part));
57                    }
58                    result
59                }
60            },
61        );
62        let mut headers: HttpResponseHeaders = new_response_headers();
63        for line in lines.by_ref() {
64            if line.is_empty() {
65                break;
66            }
67            let mut colon_pos: Option<usize> = None;
68            for (i, &byte) in line.iter().enumerate() {
69                if byte == COLON_U8 {
70                    colon_pos = Some(i);
71                    break;
72                }
73            }
74            if let Some(pos) = colon_pos
75                && pos > 0
76                && pos + 1 < line.len()
77            {
78                let key_bytes: &[u8] = &line[..pos];
79                let value_start: usize = if line.get(pos + 1) == Some(&SPACE_U8) {
80                    pos + 2
81                } else {
82                    pos + 1
83                };
84                let value_bytes: &[u8] = &line[value_start..];
85                if let (Ok(key_str), Ok(value_str)) = (from_utf8(key_bytes), from_utf8(value_bytes))
86                {
87                    headers.insert(
88                        key_str.trim().to_ascii_lowercase(),
89                        value_str.trim().to_owned(),
90                    );
91                }
92            }
93        }
94        let body: ResponseBody = match lines.len() {
95            0 => ResponseBody::new(),
96            1 => {
97                let line: &[u8] = lines.next().unwrap_or(&[]);
98                line.to_vec()
99            }
100            _ => {
101                let lines_slice: &[&[u8]] = lines.as_slice();
102                let total_size: usize = lines_slice
103                    .iter()
104                    .map(|line: &&[u8]| line.len())
105                    .sum::<usize>()
106                    + lines_slice.len().saturating_sub(1) * BR_BYTES.len();
107                let mut body: ResponseBody = ResponseBody::with_capacity(total_size);
108                let mut first: bool = true;
109                for line in lines {
110                    if !first {
111                        body.extend_from_slice(BR_BYTES);
112                    }
113                    body.extend_from_slice(line);
114                    first = false;
115                }
116                body
117            }
118        };
119        HttpResponse {
120            version,
121            status_code,
122            reason_phrase,
123            headers,
124            body,
125        }
126    }
127
128    /// Was the response a 2xx success?
129    ///
130    /// # Returns
131    ///
132    /// - `bool` - `true` when the status code lies in the `200..300` range.
133    pub fn is_success(&self) -> bool {
134        (200..300).contains(&self.get_status_code())
135    }
136
137    /// Was the response a 3xx redirect?
138    ///
139    /// # Returns
140    ///
141    /// - `bool` - `true` when the status code lies in the `300..400` range.
142    pub fn is_redirect(&self) -> bool {
143        (300..400).contains(&self.get_status_code())
144    }
145
146    /// Look up a single header value (case-insensitive).
147    ///
148    /// # Arguments
149    ///
150    /// - `K` - The header name to look up, matched case-insensitively.
151    ///
152    /// # Returns
153    ///
154    /// - `Option<&str>` - The header value, or `None` when the header is absent.
155    pub fn get_header<K: AsRef<str>>(&self, key: K) -> Option<&str> {
156        let normalized = key.as_ref().to_ascii_lowercase();
157        self.headers.get(&normalized).map(String::as_str)
158    }
159
160    /// Get the response body as a UTF-8 string (lossy on invalid bytes).
161    ///
162    /// # Returns
163    ///
164    /// - `String` - The owned body text, with invalid byte sequences replaced.
165    pub fn text(&self) -> String {
166        String::from_utf8_lossy(self.get_body()).into_owned()
167    }
168
169    /// Get the response body as raw bytes.
170    ///
171    /// # Returns
172    ///
173    /// - `&[u8]` - The raw, undecoded body bytes.
174    pub fn bytes(&self) -> &[u8] {
175        self.get_body()
176    }
177
178    /// Decode the body using the headers' Content-Encoding (gzip / deflate /
179    /// br). Returns a fresh response with the decoded body — the original is
180    /// untouched. `buffer_size` controls the chunk size for streaming decoders.
181    ///
182    /// # Arguments
183    ///
184    /// - `usize` - The chunk size used by the streaming decoders.
185    ///
186    /// # Returns
187    ///
188    /// - `HttpResponse` - A new response identical to this one except that its
189    ///   body holds the decoded bytes.
190    pub fn decode(&self, buffer_size: usize) -> HttpResponse {
191        let flat_headers: HttpResponseHeaders = self.get_headers().clone();
192        let decoded: ResponseBody = Compress::from(&flat_headers)
193            .decode(self.get_body(), buffer_size)
194            .into_owned();
195        HttpResponse {
196            version: self.get_version().clone(),
197            status_code: self.get_status_code(),
198            reason_phrase: self.get_reason_phrase().clone(),
199            headers: self.get_headers().clone(),
200            body: decoded,
201        }
202    }
203}