160 lines
4.2 KiB
Rust
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(())
|
|
}
|