static-site-server-rs/src/http_response.rs

160 lines
4.2 KiB
Rust

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<u8>,
pub content_type: Option<String>,
pub last_modified: Option<SystemTime>,
pub location: Option<String>,
pub hsts: bool,
}
impl From<TextTemplateResult> 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<String>,
last_modified: Option<SystemTime>,
location: Option<String>,
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::<u8>::default().as_slice(),
None,
Some(t),
None,
false,
)
}
pub fn generate_302<T: Into<String>>(s: T) -> HttpResponse {
HttpResponse::new(
StatusCode::FOUND,
Vec::<u8>::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<Utc> = (*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(())
}