http_request/response/impl.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
use super::r#type::{HttpResponse, HttpResponseText};
use crate::{
constant::{
common::{BR_BYTES, COLON_SPACE_BYTES},
http::{CONTENT_LENGTH, HTTP_BR, HTTP_BR_BYTES},
},
request::http_version::r#type::HttpVersion,
status_code::r#type::StatusCode,
utils::vec::{split_multi_byte, split_whitespace},
};
use std::{collections::HashMap, vec::IntoIter};
/// Provides functionality for parsing and working with HTTP responses.
///
/// This implementation contains methods for extracting specific information from HTTP response
/// strings, such as content length, and parsing the entire response into an `HttpResponse` object.
///
/// # Methods
/// - `get_content_length`: Extracts the `Content-Length` value from the HTTP response string.
/// - `from`: Parses a raw HTTP response string into an `HttpResponse` struct, including the
/// status line, headers, and body.
impl HttpResponse {
/// Extracts the `Content-Length` from the response string.
///
/// This method scans the HTTP response string for the `Content-Length` header and parses
/// its value into a `usize`. If the header is not present or its value is invalid, the method
/// returns `0` as a default.
///
/// # Parameters
/// - `response_string`: A string representing the HTTP response.
///
/// # Returns
/// Returns the `Content-Length` value extracted from the response, or `0` if not found.
pub fn get_content_length(response_string: &str) -> usize {
let content_length_sign_key: String = format!("{}:", CONTENT_LENGTH.to_lowercase());
response_string
.to_lowercase()
.find(&content_length_sign_key)
.and_then(|length_pos| {
let start: usize = length_pos + content_length_sign_key.len();
let tmp_res = response_string[start..]
.find(HTTP_BR)
.and_then(|end| {
let content_length: usize = response_string[start..start + end]
.trim()
.parse()
.unwrap_or(0);
Some(content_length)
})
.unwrap_or_default();
Some(tmp_res)
})
.unwrap_or_default()
}
/// Parses an HTTP response from a byte slice and returns an `HttpResponse` object.
///
/// This function processes the raw HTTP response in byte form. It splits the response into
/// the status line, headers, and body, parsing each part accordingly. The status line is parsed
/// to extract the HTTP version, status code, and status text. Headers are split and stored in
/// a `HashMap`. The body is collected into a byte vector.
///
/// # Parameters
/// - `response`: A byte slice representing the raw HTTP response.
///
/// # Returns
/// Returns an `HttpResponse` object containing the parsed HTTP version, status code, status text,
/// headers, and body. If parsing any part fails, defaults are used (e.g., `HTTP/1.1`, status code `200`).
///
/// # Panics
/// This method will panic if the HTTP response is malformed in ways that the unwrap operations cannot handle.
pub fn from(response: &[u8]) -> Self {
let split_lines: Vec<&[u8]> = split_multi_byte(response, HTTP_BR_BYTES);
let mut lines: IntoIter<&[u8]> = split_lines.into_iter();
let status_line: &[u8] = lines.next().unwrap_or(&[]);
let status_parts: Vec<&[u8]> = split_whitespace(&status_line);
let http_version: String = String::from_utf8_lossy(
status_parts
.get(0)
.unwrap_or(&HttpVersion::Unknown(String::new()).to_string().as_bytes()),
)
.to_string();
let status_code: u16 = status_parts
.get(1)
.and_then(|part| std::str::from_utf8(part).ok())
.unwrap_or(&StatusCode::Ok.to_string())
.parse()
.unwrap_or(StatusCode::Unknown.code());
let status_text: String = status_parts.get(2..).map_or_else(
|| StatusCode::Unknown.to_string(),
|parts| String::from_utf8_lossy(&parts.concat()).to_string(),
);
let mut headers: HashMap<String, String> = HashMap::new();
while let Some(line) = lines.next() {
if line.is_empty() {
break;
}
let header_parts: Vec<&[u8]> = split_multi_byte(&line, COLON_SPACE_BYTES);
if header_parts.len() == 2 {
let key: String = String::from_utf8_lossy(header_parts[0]).trim().to_string();
let value: String = String::from_utf8_lossy(header_parts[1]).trim().to_string();
headers.insert(key, value);
}
}
let body: Vec<u8> = lines.clone().collect::<Vec<&[u8]>>().join(BR_BYTES);
HttpResponse {
http_version,
status_code,
status_text,
headers,
body,
}
}
/// Converts the response body to text format.
///
/// This function takes the current response and creates a new `HttpResponse`
/// instance with the body converted to a text representation. The `body` is
/// extracted as text from the original response body and stored in the new
/// response as a `ResponseBody::Text` variant.
///
/// # Returns
///
/// - `Self` - A new `HttpResponse` instance with the body converted to text.
pub fn text(self) -> HttpResponseText {
let res: HttpResponse = self.clone();
let body: String = String::from_utf8_lossy(&res.body).to_string();
HttpResponseText {
http_version: res.http_version,
status_code: res.status_code,
status_text: res.status_text,
headers: res.headers,
body,
}
}
}
/// Default implementation for `HttpResponse`.
///
/// This implementation provides default values for an `HttpResponse` instance, setting the HTTP
/// version to the default version, the status code to `StatusCode::Unknown`, and initializing the
/// headers and body to empty collections.
impl Default for HttpResponse {
fn default() -> Self {
HttpResponse {
http_version: HttpVersion::Unknown(String::new()).to_string(),
status_code: StatusCode::Unknown.code(),
status_text: StatusCode::Unknown.to_string(),
headers: HashMap::new(),
body: Vec::new(),
}
}
}