#![allow(dead_code)]
pub(crate) mod pre_built_resp;
extern crate chrono;
use crate::cache::{Cache,FileData};
use crate::error;
use crate::utils::{ContentType, HTTPCode};
use crate::utils::{FileType, Protocall};
use crate::File::FileWrapper;
use chrono::format::strftime::StrftimeItems;
use chrono::offset::Utc;
use pre_built_resp::{InternalServerError, Notfound404};
use std::borrow::Cow;
use std::collections::HashMap;
use std::fs::File;
use std::io;
use std::net::TcpStream;
use std::ptr::NonNull;
use std::sync::Arc;
use std::time::{Duration, Instant, UNIX_EPOCH};
use std::{io::Write, path::PathBuf};
pub struct HttpResponse {
pub(crate) code: HTTPCode,
pub content_type: ContentType,
pub content_len: usize,
pub content: Option<String>,
pub file_response: bool,
pub file_content_location: Option<PathBuf>,
pub file_name: Option<String>,
pub headers: HashMap<String, String>,
pub keep_alive: bool,
}
impl HttpResponse {
pub(crate) fn new(
resp_code: HTTPCode,
content_type: ContentType,
content_len: usize,
content: Option<String>,
file_response: bool,
file_content_location: Option<PathBuf>,
filename: Option<String>,
keep_alive: bool,
) -> HttpResponse {
let headers = HashMap::new();
HttpResponse {
code: resp_code,
content_type: content_type,
content_len: content_len,
content: content,
file_response: file_response,
file_content_location: file_content_location,
headers: headers,
file_name: filename,
keep_alive: keep_alive,
}
}
pub fn add_header(&mut self, key: &str, value: &str) {
self.headers.insert(String::from(key), String::from(value));
}
fn validate_etag(
file_len: usize,
last_updated: usize,
recieved_etag: Option<String>,
) -> (bool, String) {
let mut cur_etag = last_updated.to_string();
cur_etag.push('@');
cur_etag.push_str(&file_len.to_string());
if recieved_etag.is_none() {
(false, cur_etag)
} else {
(cur_etag == recieved_etag.unwrap(), cur_etag)
}
}
fn to_string(&self, protocall: Protocall) -> String {
let mut resp = String::new();
resp.push_str(protocall.to_str());
resp.push(' ');
resp.push_str(&format!("{} {}\r\n", self.code as u16, self.code.get_msg()));
let cur_time =
Utc::now().format_with_items(StrftimeItems::new("%a, %d %b %Y %H:%M:%S GMT"));
resp.push_str(&format!("Date: {}\r\n", cur_time));
for (key, val) in self.headers.iter() {
resp.push_str(&format!("{}: {}\r\n", key, val));
}
let content_type = self.content_type.get_content_type_header();
resp.push_str(content_type);
resp.push_str("\r\n");
resp
}
pub(crate) fn send_response(
&mut self,
stream: Arc<TcpStream>,
protocall: Protocall,
write_time_out: Duration,
cache: NonNull<Cache<PathBuf>>,
etag_value_recieved: Option<String>,
send_buffer_size:usize
) -> Result<u16, io::Error> {
if let Some(content) = &self.content {
let mut resp = self.to_string(protocall);
resp.push_str(content);
let resp_bytes = resp.as_bytes();
let mut resp_len = resp_bytes.len();
let mut last_updated = Instant::now();
while resp_len > 0 {
match stream.as_ref().write(&resp_bytes[resp.len() - resp_len..]) {
Ok(n) => {
resp_len -= n;
last_updated = Instant::now();
}
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
if last_updated.elapsed() > write_time_out {
return Err(e);
}
continue;
}
Err(e) => {
return Err(e);
}
}
}
} else {
if self.file_response {
return self.send_file_response(
stream,
protocall,
write_time_out,
cache,
etag_value_recieved,
send_buffer_size
);
}
}
Ok(self.code as u16)
}
fn send_file_response(
&mut self,
stream: Arc<TcpStream>,
_protocall: Protocall,
write_time_out: Duration,
#[allow(unused_variables)]
cache: NonNull<Cache<PathBuf>>,
etag_value_recieved: Option<String>,
send_buffer_size:usize
) -> Result<u16, io::Error> {
if let Some(file_location) = self.file_content_location.clone() {
let file = File::open(&file_location)?;
let (file_len, file_last_updated) = match file.metadata() {
Ok(m) => (
m.len(),
m.modified()?.duration_since(UNIX_EPOCH).unwrap().as_secs(),
),
Err(e) => {
return Err(e);
}
};
let (valid, cur_etag) = Self::validate_etag(
file_len as usize,
file_last_updated as usize,
etag_value_recieved,
);
if valid {
return Self::send_304_response(stream.clone(), _protocall, cur_etag);
}
else {
self.add_header("Etag", &cur_etag);
let metadata = self.to_string(_protocall);
#[cfg(feature="caching")]
if let Some(cache_res) = unsafe { cache.as_ref() }.get(file_location.clone()){
if cache_res.val.len()==file_len as usize{
let _=match Self::send_response_metadata(stream.clone(), &metadata, send_buffer_size,write_time_out){
Ok(())=>(),
Err(e)=>{
return Err(e);
}
};
return self.send_cached_response(stream.clone(), &cache_res, send_buffer_size, write_time_out);
}
};
let _=match Self::send_response_metadata(stream.clone(), &metadata, send_buffer_size,write_time_out){
Ok(())=>(),
Err(e)=>{
return Err(e);
}
};
#[allow(unused_variables)]
let file_data=match Self::write_file_from_disk_to_network(stream.clone(), file, send_buffer_size, write_time_out){
Ok(v)=>{
v.into_vec()
}
Err(e)=>{
return Err(e);
}
};
#[cfg(feature="caching")]
unsafe { cache.as_ref() }.insert(file_location.clone(), &file_data);
}
return Ok(self.code as u16);
}
Err(io::ErrorKind::NotFound.into())
}
fn send_304_response(stream: Arc<TcpStream>,protocall: Protocall,etag:String)->Result<u16,io::Error>
{
let mut resp = String::new();
resp.push_str(protocall.to_str());
resp.push(' ');
resp.push_str(&format!("{} {}\r\n", 304, HTTPCode::NotModified.get_msg()));
let cur_time =
Utc::now().format_with_items(StrftimeItems::new("%a, %d %b %Y %H:%M:%S GMT"));
resp.push_str(&format!("Date: {}\r\n", cur_time));
resp.push_str(&format!("Etag: {}\r\n", &etag));
resp.push_str("\r\n");
let _ = match stream.as_ref().write_all(resp.as_bytes()) {
Ok(_) => {
return Ok(304);
}
Err(e) => {
return Err(e);
}
};
}
fn send_cached_response(&self,stream: Arc<TcpStream>,data:&FileData,send_buffer_size:usize,write_time_out: Duration)->Result<u16,io::Error>{
for chunk in data.val.chunks(send_buffer_size){
let mut bytes_left_to_write:usize=0;
let total_bytes_to_write=chunk.len();
let mut last_written=Instant::now();
while bytes_left_to_write<total_bytes_to_write {
let _=match stream.as_ref().write(&chunk[bytes_left_to_write..]){
Ok(0)=>{
return Err(io::ErrorKind::WriteZero.into());
},
Ok(n)=>{
last_written=Instant::now();
bytes_left_to_write+=n;
},
Err(e) if e.kind()==io::ErrorKind::WouldBlock=>{
if last_written.elapsed()>write_time_out{
return Err(e);
}
continue;
},
Err(e)=>{
return Err(e);
}
};
}
}
Ok(self.code as u16)
}
fn send_response_metadata(stream: Arc<TcpStream>,metadata:&str,send_buffer_size:usize,write_time_out: Duration)->Result<(),io::Error>{
let metadat_bytes=metadata.as_bytes();
for chunk in metadat_bytes.chunks(send_buffer_size){
let mut bytes_written=0_usize;
let mut last_written=Instant::now();
let total_bytes_to_write=chunk.len();
while bytes_written<total_bytes_to_write{
let _=match stream.as_ref().write(&chunk[bytes_written..]){
Ok(n)=>{
if n==0{
return Err(io::ErrorKind::WriteZero.into());
}
last_written=Instant::now();
bytes_written+=n;
}
Err(e) if e.kind()==io::ErrorKind::WouldBlock=>{
if last_written.elapsed() > write_time_out{
return Err(io::ErrorKind::WouldBlock.into());
}
continue;
}
Err(e)=>{
return Err(e);
}
};
}
}
Ok(())
}
fn write_file_from_disk_to_network(stream:Arc<TcpStream>,file:File,send_buffer_size:usize,write_time_out: Duration)->Result<Box<[u8]>,io::Error>{
#[allow(unused_mut)]
let mut file_data = Vec::<u8>::new();
let mut file_wrapper = FileWrapper::new(file, Some(send_buffer_size));
for chunk in file_wrapper.iter() {
match chunk {
Ok(v) => {
#[cfg(feature="caching")]
file_data.extend_from_slice(&v);
let mut bytes_written=0_usize;
let total_bytes_to_write = v.len();
let mut last_written = Instant::now();
while bytes_written < total_bytes_to_write {
match stream.as_ref().write(&v[bytes_written..]){
Ok(n)=>{
if n==0
{
return Err(io::ErrorKind::WriteZero.into());
}
last_written=Instant::now();
bytes_written+=n;
}
Err(e) if e.kind()==io::ErrorKind::WouldBlock=>{
if last_written.elapsed() > write_time_out{
return Err(e);
}
continue;
},
Err(e)=>{
return Err(e);
}
}
}
}
Err(e) => {
return Err(e);
}
}
}
Ok(file_data.into_boxed_slice())
}
}
pub fn create_response(
content: &str,
http_code: u16,
content_type: ContentType,
keep_alive: bool,
) -> Result<HttpResponse, io::Error> {
let resp_code = match HTTPCode::from_u16(http_code) {
Some(c) => c,
None => {
error!("HTTP Code of {} is not implimented.", http_code);
return Err(io::ErrorKind::InvalidData.into());
}
};
let content_len = content.len();
let mut resp = HttpResponse::new(
resp_code,
content_type,
content_len,
Some(content.to_string()),
false,
None,
None,
keep_alive,
);
resp.add_header("Content-Length", content_len.to_string().as_str());
return Ok(resp);
}
pub fn send_file(
file_location: &str,
mut file_name: Option<String>,
file_type: FileType,
http_code: u16,
keep_alive: bool,
) -> Result<HttpResponse, io::Error> {
let resp_code = match HTTPCode::from_u16(http_code) {
Some(c) => c,
None => {
error!("HTTP Code of {} is not implimented.", http_code);
return Err(io::ErrorKind::InvalidData.into());
}
};
let file_location_path_buf = PathBuf::from(file_location);
let file = match File::open(&file_location) {
Ok(f) => f,
Err(_e) => {
return Ok(Notfound404(Cow::Borrowed(
"File doesn't exist or has been deleted.",
)));
}
};
let file_metadata = match file.metadata() {
Ok(m) => m,
Err(_e) => {
return Ok(InternalServerError(""));
}
};
let file_len = file_metadata.len();
if file_name.is_none() {
file_name = file_location_path_buf
.file_name()
.map(|os_str| os_str.to_string_lossy().into_owned());
}
let mut resp = HttpResponse::new(
resp_code,
file_type.to_content_type(),
file_len as usize,
None,
true,
Some(file_location_path_buf),
file_name,
keep_alive,
);
resp.add_header("Content-Length", file_len.to_string().as_str());
if let Some(_f_) = &resp.file_name {
let val = format!(r#"attachment; filename="{}""#, _f_);
resp.add_header("Content-Disposition", val.as_str());
}
Ok(resp)
}