mod response_types;
mod responselike;
pub use responselike::ResponseLike;
use std::{collections::HashMap, fmt, io};
use crate::HttpVersion;
pub const DEFAULT_HTTP_VERSION: HttpVersion = HttpVersion::V1_1;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Response {
pub version: HttpVersion,
pub status: u16,
pub status_text: &'static str,
pub bytes: Vec<u8>,
pub headers: Option<Headers>,
}
pub type Headers = HashMap<&'static str, String>;
impl Response {
pub fn new(
version: HttpVersion,
status: u16,
status_text: &'static str,
bytes: Vec<u8>,
headers: Option<Headers>,
) -> Self {
Self {
version,
status,
status_text,
bytes,
headers,
}
}
pub fn send_to<T: io::Write>(&mut self, stream: &mut T) -> Result<(), io::Error> {
let prev = self.prepare_response().into_bytes();
stream.write_all(&prev)?;
stream.write_all(&self.bytes)?;
stream.flush()
}
pub fn with_header(mut self, key: &'static str, value: String) -> Self {
self.headers
.get_or_insert_with(HashMap::new)
.insert(key, value);
self
}
pub fn with_content_type(self, value: String) -> Self {
self.with_header("Content-Type", value)
}
pub fn set_header(&mut self, key: &'static str, value: String) -> &mut Self {
self.headers
.get_or_insert_with(HashMap::new)
.insert(key, value);
self
}
pub fn set_content_length(&mut self, len: usize) -> &mut Self {
self.set_header("Content-Length", len.to_string())
}
fn prepare_response(&self) -> String {
let mut text = format!("{} {} {}\r\n", self.version, self.status, self.status_text);
if let Some(headers) = &self.headers {
for (key, value) in headers {
text.push_str(&format!("{key}: {value}\r\n"));
}
}
text += "\r\n";
text
}
pub fn to_bytes(&mut self) -> Vec<u8> {
let mut bytes = self.prepare_response().into_bytes();
bytes.append(&mut self.bytes);
bytes
}
pub fn len(&self) -> usize {
self.bytes.len()
}
pub fn is_empty(&self) -> bool {
self.bytes.is_empty()
}
pub fn with_default_headers(mut self) -> Self {
let now = chrono::Utc::now().to_rfc2822();
let len = self.len();
self.set_header("Content-Length", len.to_string())
.set_header("Date", now)
.set_header("Server", "Snowboard".into());
self
}
pub(crate) fn maybe_add_defaults(mut self, should_insert: bool) -> Self {
if should_insert {
self = self.with_default_headers();
}
self
}
}
impl From<Response> for Vec<u8> {
fn from(mut res: Response) -> Self {
res.to_bytes()
}
}
impl fmt::Display for Response {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut text = self.prepare_response();
text += String::from_utf8_lossy(&self.bytes).as_ref();
write!(f, "{}", text)
}
}
impl Default for Response {
fn default() -> Self {
Self {
version: DEFAULT_HTTP_VERSION,
status: 200,
status_text: "Ok",
bytes: vec![],
headers: None,
}
}
}