use dashmap::DashMap;
use crate::http::cookie::{CookieJar, Cookie};
pub type PotentialResponse = Option<Response>;
pub type ResponseBytes = Vec<u8>;
pub type ResponseHeaders = DashMap<String, String>;
#[derive(Debug, Clone)]
pub struct Response {
pub protocol: String,
pub status: u16,
pub body: String,
pub headers: ResponseHeaders,
pub cookies: CookieJar,
}
impl Response {
pub fn new() -> Self {
let res = Self {
protocol: "HTTP/1.1".to_string(),
status: 200,
body: "".to_string(),
headers: DashMap::new(),
cookies: CookieJar::new(),
};
return res;
}
pub fn raw(&self) -> String {
let mut header_string = String::new(); for header in &self.headers {
let (key, value) = header.pair();
header_string.push_str(&format!("{}: {}\r\n", key, value));
}
for cookie in &self.cookies.cookies {
header_string.push_str(&format!("Set-Cookie: {}\r\n", cookie.to_string()));
}
let full_response = format!(
"{} {}\r\n{}\r\n{}",
self.protocol,
self.status,
header_string,
self.body
);
return full_response;
}
pub fn status(mut self, status: u16) -> Self {
self.status = status;
return self;
}
pub fn body(mut self, body: &str) -> Self {
self.body = body.to_string();
self.headers.insert("Content-Length".to_string(), body.len().to_string());
return self;
}
pub fn new_from_bytes(response_bytes: &Vec<u8>) -> Response {
let mut response = Response::new();
let end = response_bytes.iter().position(|&x| x == 0).unwrap_or(response_bytes.len());
let request_string = String::from_utf8(response_bytes[..end].to_vec());
match request_string {
Err(e) => {
return Response::new()
.status(500)
.body(&format!("internal server error: {:?}", e));
}
Ok(request_string) => {
let lines: Vec<&str> = request_string.lines().collect();
for i in 0..lines.len() {
let line = lines[i];
if line.len() == 0 {
continue;
}
if i == 0 {
let parts: Vec<&str> = line.split(" ").collect();
if parts.len() < 2 {
return Response::new()
.status(400)
.body("malformed response, more than 2 parts in status line");
}
let protocol = parts[0];
let status = parts[1];
match status.parse::<u16>() {
Ok(status) => {
response.status = status;
}
Err(_) => {
return Response::new()
.status(400)
.body("malformed response, status is not a number");
}
}
response.protocol = protocol.to_string();
continue;
}
if i == lines.len() - 1 {
let line = line.trim();
if line.len() == 0 {
continue;
}
response.body = line.to_string();
continue;
}
if !line.contains(":") {
continue;
}
let parts: Vec<&str> = line.split(":").collect();
if parts.len() < 2 {
return Response::new()
.status(400)
.body("malformed response, more than 2 parts in header line");
}
let key = parts[0].trim();
let value = parts[1].trim();
if key.to_lowercase() != "set-cookie" {
response.headers.insert(key.to_string(), value.to_string());
continue;
}
println!("key: {}, value: {}", key, value);
}
return response;
}
}
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut header_string = String::new(); for header in &self.headers {
let (key, value) = header.pair();
header_string.push_str(&format!("{}: {}\r\n", key, value));
}
for cookie in &self.cookies.cookies {
header_string.push_str(&format!("Set-Cookie: {}\r\n", cookie.to_string()));
}
let full_response = format!(
"HTTP/1.1 {}\r\n{}\r\n{}",
self.status,
header_string,
self.body
);
full_response.into_bytes() }
pub fn set_header(mut self, key: &str, value: &str) -> Self {
self.headers.insert(key.to_string(), value.to_string());
return self;
}
pub fn get_header(&self, key: &str) -> String {
let header = self.headers.get(key);
if header.is_none() {
return "".to_string();
}
return header.unwrap().to_string();
}
pub fn set_cookie(mut self, cookie: Cookie) -> Self {
self.cookies.add(cookie);
self
}
pub fn get_cookie(&self, key: &str) -> String {
let cookies = self.headers.get("Cookie");
if cookies.is_none() {
return "".to_string();
}
let cookies = cookies.unwrap();
let cookies = cookies.to_owned();
let cookies = cookies.split(";").collect::<Vec<&str>>();
for cookie in cookies {
let parts = cookie.split("=").collect::<Vec<&str>>();
if parts.len() != 2 {
continue
}
if parts[0] == key {
return parts[1].to_string();
}
}
return "".to_string();
}
}
pub fn not_found() -> Response {
Response {
protocol: "HTTP/1.1".to_string(),
status: 404,
body: "Not Found".to_string(),
headers: DashMap::new(),
cookies: CookieJar::new(),
}
}