mod response_types;
mod responselike;
pub use responselike::ResponseLike;
use smol::io::{AsyncWrite, AsyncWriteExt};
use std::{collections::HashMap, fmt, fmt::Write, io};
use crate::HttpVersion;
pub const DEFAULT_HTTP_VERSION: HttpVersion = HttpVersion::V1_1;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Response {
pub(crate) version: HttpVersion,
pub status: u16,
pub status_text: &'static str,
pub bytes: Vec<u8>,
pub headers: 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: Headers,
) -> Self {
Self {
version,
status,
status_text,
bytes,
headers,
}
}
pub async fn send_to<T: AsyncWrite + Unpin>(
&mut self,
stream: &mut T,
) -> Result<(), io::Error> {
self.set_content_length();
let prev = self.prepare_response().into_bytes();
stream.write_all(&prev).await?;
stream.write_all(&self.bytes).await?;
stream.write_all(b"\r\n").await?;
stream.flush().await
}
pub fn with_header(mut self, key: &'static str, value: String) -> Self {
self.headers.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.insert(key, value);
self
}
pub fn set_content_length(&mut self) -> &mut Self {
self.set_header("Content-Length", (self.len() + 2).to_string())
}
fn prepare_response(&self) -> String {
let estimated_size = 50 + (self.headers.len() * 100) + 2;
let mut text = String::with_capacity(estimated_size);
writeln!(
text,
"{} {} {}",
self.version, self.status, self.status_text
)
.unwrap();
for (key, value) in self.headers.iter() {
writeln!(text, "{}: {}", key, value).unwrap();
}
text.push_str("\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();
text += "\r\n";
write!(f, "{}", text)
}
}
impl Default for Response {
fn default() -> Self {
Self {
version: DEFAULT_HTTP_VERSION,
status: 200,
status_text: "OK",
bytes: vec![],
headers: Headers::default(),
}
}
}