use std::{io::Write, net::TcpStream, time::SystemTime}; use chrono::{DateTime, Utc}; use http::StatusCode; use serde::{Deserialize, Serialize}; use crate::{ text_template_result::TextTemplateResult, utils::{render_http500, STATIC_404_DEFAULT}, }; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct HttpResponse { #[serde(skip, default = "StatusCode::default")] pub http_code: StatusCode, pub payload: Vec, pub content_type: Option, pub last_modified: Option, pub location: Option, pub hsts: bool, } impl From for HttpResponse { fn from(buffer: TextTemplateResult) -> Self { HttpResponse::new( StatusCode::OK, buffer.text.as_bytes(), buffer.mime, buffer.time, None, false, ) } } impl HttpResponse { pub fn new( http_code: StatusCode, payload: &[u8], content_type: Option, last_modified: Option, location: Option, hsts: bool, ) -> Self { Self { http_code, payload: payload.to_vec(), content_type, last_modified, location, hsts, } } pub fn send_reply(&self, stream: &TcpStream) -> std::io::Result<()> { reply_http11_response( stream, &self.http_code, self.payload.as_slice(), self.content_type.as_ref(), self.last_modified.as_ref(), self.location.as_ref(), self.hsts, ) } pub fn with_hsts(mut self, hsts: bool) -> Self { self.hsts = hsts; self } pub fn with_last_modified(mut self, last_modified: Option<&SystemTime>) -> Self { self.last_modified = last_modified.copied(); self } pub fn generate_304(t: SystemTime) -> HttpResponse { HttpResponse::new( StatusCode::NOT_MODIFIED, Vec::::default().as_slice(), None, Some(t), None, false, ) } pub fn generate_302>(s: T) -> HttpResponse { HttpResponse::new( StatusCode::FOUND, Vec::::default().as_slice(), None, None, Some(s.into()), false, ) } pub fn generate_404() -> HttpResponse { HttpResponse::new( StatusCode::NOT_FOUND, STATIC_404_DEFAULT.as_bytes(), Some("text/html; charset=utf-8".to_string()), None, None, false, ) } pub fn generate_500(reason: Option<&str>) -> HttpResponse { HttpResponse::new( StatusCode::INTERNAL_SERVER_ERROR, render_http500(reason).as_bytes(), Some("text/html; charset=utf-8".to_string()), None, None, false, ) } } fn reply_http11_response( mut stream: &TcpStream, http_code: &StatusCode, payload: &[u8], content_type: Option<&String>, last_modified: Option<&SystemTime>, location: Option<&String>, hsts: bool, ) -> std::io::Result<()> { stream.write_all( format!( "HTTP/1.1 {} {}\r\n", http_code.as_u16(), http_code.canonical_reason().unwrap_or("Unknown") ) .as_bytes(), )?; stream.write_all("Connection: close\r\n".as_bytes())?; stream.write_all(format!("Content-Length: {}\r\n", payload.len()).as_bytes())?; if let Some(x) = content_type { stream.write_all(format!("Content-Type: {}\r\n", x).as_bytes())? }; if let Some(modified_date) = last_modified { let dt: DateTime = (*modified_date).into(); let date = dt.format("%a, %d %b %Y %H:%M:%S %z"); stream.write_all(format!("Last-Modified: {}\r\n", date).as_bytes())? }; if let Some(next_location) = location { stream.write_all(format!("Location: {}\r\n", next_location).as_bytes())? }; if hsts { stream.write_all("Strict-Transport-Security: max-age=31536000\r\n".as_bytes())? } stream.write_all("\r\n".as_bytes())?; stream.write_all(payload)?; stream.flush()?; Ok(()) }