use std::{ collections::HashMap, fmt::{Debug, Formatter, Result as FmtResult}, }; use chrono::Local; use crate::Cookie; #[derive(Clone)] pub struct Response { pub(crate) body: Vec, pub(crate) status: u16, pub(crate) headers: HashMap>, } impl Response { pub fn new() -> Self { Response { body: vec![], status: 200, headers: HashMap::new(), } } pub fn get_status(&self) -> u16 { self.status } pub fn get_status_message(&self) -> &str { match self.status { 100 => "continue", 101 => "switching protocols", 102 => "processing", 200 => "ok", 201 => "created", 202 => "accepted", 203 => "non-authoritative information", 204 => "no content", 205 => "reset content", 206 => "partial content", 207 => "multi-status", 208 => "already reported", 226 => "im used", 300 => "multiple choices", 301 => "moved permanently", 302 => "found", 303 => "see other", 304 => "not modified", 305 => "use proxy", 307 => "temporary redirect", 308 => "permanent redirect", 400 => "bad request", 401 => "unauthorized", 402 => "payment required", 403 => "forbidden", 404 => "not found", 405 => "method not allowed", 406 => "not acceptable", 407 => "proxy authentication required", 408 => "request timeout", 409 => "conflict", 410 => "gone", 411 => "length required", 412 => "precondition failed", 413 => "payload too large", 414 => "uri too long", 415 => "unsupported media type", 416 => "range not satisfiable", 417 => "expectation failed", 418 => "I'm a teapot", 422 => "unprocessable entity", 423 => "locked", 424 => "failed dependency", 426 => "upgrade required", 428 => "precondition required", 429 => "too many requests", 431 => "request header fields too large", 500 => "internal server error", 501 => "not implemented", 502 => "bad gateway", 503 => "service unavailable", 504 => "gateway timeout", 505 => "http version not supported", 506 => "variant also negotiates", 507 => "insufficient storage", 508 => "loop detected", 510 => "not extended", 511 => "network authentication required", _ => "unknown", } } pub fn set_status(&mut self, code: u16) { self.status = code; } pub fn set_content_length(&mut self, length: usize) { self.set_header("content-length", &format!("{}", length)); } pub fn get_headers(&self) -> &HashMap> { &self.headers } pub fn get_header(&self, field: &str) -> Option { self.headers.iter().find_map(|(key, value)| { if key.to_lowercase() == field.to_lowercase() { Some(value.join("\n")) } else { None } }) } pub fn set_header(&mut self, key: &str, value: &str) { self.headers .insert(key.to_string(), vec![value.to_string()]); } pub fn append_header(&mut self, key: &str, value: &str) { if let Some(headers) = self.headers.get_mut(key) { headers.push(value.to_string()); } else { self.headers .insert(key.to_string(), vec![value.to_string()]); } } pub fn remove_header(&mut self, field: &str) { self.headers.remove(field); } pub fn get_content_type(&self) -> String { let c_type = self .get_header("Content-Type") .unwrap_or_else(|| "text/plain".to_string()); c_type.split(';').next().unwrap_or("").to_string() } pub fn set_content_type(&mut self, content_type: &str) { self.set_header("Content-Type", content_type); } pub fn redirect(&mut self, url: &str) { if self.status == 200 { self.set_status(302); } self.set_header("Location", url); } pub fn attachment(&mut self, file_name: Option<&str>) { self.set_header( "Content-Disposition", &format!( "attachment{}", if let Some(filename) = file_name { format!("; filename=\"{}\"", filename) } else { String::new() } ), ); } pub fn get_last_modified(&self) -> Option { self.get_header("Last-Modified") } pub fn set_last_modified(&mut self, last_modified: &str) { self.set_header("Last-Modified", last_modified); } pub fn set_etag(&mut self, etag: &str) { self.set_header("ETag", etag); } pub fn get_body(&self) -> &Vec { &self.body } pub fn set_body(&mut self, data: &str) { self.set_body_bytes(data.as_bytes()); } pub fn set_body_bytes(&mut self, data: &[u8]) { self.body = data.to_vec(); self.set_content_length(data.len()); self.set_header("date", &Local::now().to_rfc2822()); } pub fn set_cookie(&mut self, cookie: Cookie) { self.append_header("Set-Cookie", &cookie.to_header_string()); } } impl Debug for Response { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { if cfg!(debug_assertions) { f.debug_struct("Request") .field("body", &self.body) .field("status", &self.status) .field("headers", &self.headers) .finish() } else { write!(f, "[Response {}]", self.status) } } } impl Default for Response { fn default() -> Self { Response { body: vec![], status: 200, headers: HashMap::new(), } } }