use std::borrow::Borrow;
use std::{env, fs};
use std::fs::File;
use std::io::{BufReader, Read, Write};
use std::cmp::min;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use crate::core::New;
use crate::entry_point::config_file::override_environment_variables_from_config;
use file_ext::FileExt;
use crate::header::Header;
use crate::http::VERSION;
use crate::mime_type::MimeType;
use crate::range::Range;
use crate::request::{METHOD, Request};
use crate::response::{Response, STATUS_CODE_REASON_PHRASE};
use crate::server::Server;
use crate::symbol::SYMBOL;
pub struct MockTcpStream {
pub read_data: Vec<u8>,
pub write_data: Vec<u8>,
}
impl Read for MockTcpStream {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let size : usize = min(self.read_data.len(), buf.len());
buf[..size].copy_from_slice(&self.read_data[..size]);
Ok(size)
}
}
impl Write for MockTcpStream {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.write_data = Vec::from(buf);
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
impl Unpin for MockTcpStream {}
#[test]
fn it_generates_successful_response_with_index_html() {
let request_host_header_name = "Host";
let request_host_header_value = "localhost:7777";
let request_method = METHOD.get;
let request_uri = SYMBOL.slash;
let request_http_version = VERSION.http_1_1.to_string();
let host = Header {
name: request_host_header_name.to_string(),
value: request_host_header_value.to_string()
};
let headers = vec![host];
let request = Request {
method: request_method.to_string(),
request_uri: request_uri.to_string(),
http_version: request_http_version.to_string(),
headers,
body: vec![],
};
let raw_request = Request::_generate_request(request);
let request: Request = Request::parse_request(&raw_request.as_bytes()).unwrap();
let host_header = request.get_header(request_host_header_name.to_string()).unwrap();
assert_eq!(request_host_header_value.to_string(), host_header.value);
assert_eq!(request_method.to_string(), request.method);
assert_eq!(request_uri.to_string(), request.request_uri);
assert_eq!(request_http_version.to_string(), request.http_version);
let response_http_version = VERSION.http_1_1.to_string();
let response_status_code = STATUS_CODE_REASON_PHRASE.n200_ok.status_code;
let response_reason_phrase = STATUS_CODE_REASON_PHRASE.n200_ok.reason_phrase;
let response_filepath = "index.html";
let response_html_file= fs::read_to_string(response_filepath.to_string()).unwrap();
let response_content_length_header_name = "Content-Length";
let response_content_length_header_value = response_html_file.len().to_string();
let mock_tcp_stream = MockTcpStream {
read_data: raw_request.as_bytes().to_vec(),
write_data: vec![],
};
let peer_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0,0,0,0)), 0);
let raw_response: Vec<u8> = Server::process_request(mock_tcp_stream, peer_addr);
let response = Response::_parse_response(raw_response.borrow());
let header = response._get_header(response_content_length_header_name.to_string()).unwrap();
let content_type_header = response._get_header(Header::_CONTENT_TYPE.to_string()).unwrap();
let x_content_type_options_header = response._get_header(Header::_X_CONTENT_TYPE_OPTIONS.to_string()).unwrap();
assert_eq!(Header::_X_CONTENT_TYPE_OPTIONS_VALUE_NOSNIFF, x_content_type_options_header.value);
assert_eq!(MimeType::TEXT_HTML, content_type_header.value);
assert_eq!(response_content_length_header_value, header.value);
assert_eq!(response_http_version, response.http_version);
assert_eq!(*response_status_code, response.status_code);
assert_eq!(response_reason_phrase, response.reason_phrase);
assert_eq!(response_html_file.as_bytes().to_vec(), response.content_range_list.get(0).unwrap().body);
}
#[test]
#[cfg(target_family = "unix")]
fn it_generates_successful_response_with_index_html_as_symlink() {
let request_host_header_name = "Host";
let request_host_header_value = "localhost:7777";
let request_method = METHOD.get;
let request_uri = [SYMBOL.slash, "index_rewrite"].join("");
let request_http_version = VERSION.http_1_1.to_string();
let host = Header {
name: request_host_header_name.to_string(),
value: request_host_header_value.to_string()
};
let headers = vec![host];
let request = Request {
method: request_method.to_string(),
request_uri: request_uri.to_string(),
http_version: request_http_version.to_string(),
headers,
body: vec![],
};
let raw_request = Request::_generate_request(request);
let request: Request = Request::parse_request(&raw_request.as_bytes()).unwrap();
let host_header = request.get_header(request_host_header_name.to_string()).unwrap();
assert_eq!(request_host_header_value.to_string(), host_header.value);
assert_eq!(request_method.to_string(), request.method);
assert_eq!(request_uri.to_string(), request.request_uri);
assert_eq!(request_http_version.to_string(), request.http_version);
let response_http_version = VERSION.http_1_1.to_string();
let response_status_code = STATUS_CODE_REASON_PHRASE.n200_ok.status_code;
let response_reason_phrase = STATUS_CODE_REASON_PHRASE.n200_ok.reason_phrase;
let response_filepath = "index.html";
let response_html_file= fs::read_to_string(response_filepath.to_string()).unwrap();
let response_content_length_header_name = "Content-Length";
let response_content_length_header_value = response_html_file.len().to_string();
let mock_tcp_stream = MockTcpStream {
read_data: raw_request.as_bytes().to_vec(),
write_data: vec![],
};
let peer_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0,0,0,0)), 0);
let raw_response: Vec<u8> = Server::process_request(mock_tcp_stream, peer_addr);
let response = Response::_parse_response(raw_response.borrow());
let header = response._get_header(response_content_length_header_name.to_string()).unwrap();
let content_type_header = response._get_header(Header::_CONTENT_TYPE.to_string()).unwrap();
let x_content_type_options_header = response._get_header(Header::_X_CONTENT_TYPE_OPTIONS.to_string()).unwrap();
assert_eq!(Header::_X_CONTENT_TYPE_OPTIONS_VALUE_NOSNIFF, x_content_type_options_header.value);
assert_eq!(MimeType::TEXT_HTML, content_type_header.value);
assert_eq!(response_content_length_header_value, header.value);
assert_eq!(response_http_version, response.http_version);
assert_eq!(*response_status_code, response.status_code);
assert_eq!(response_reason_phrase, response.reason_phrase);
assert_eq!(response_html_file.as_bytes().to_vec(), response.content_range_list.get(0).unwrap().body);
}
#[test]
fn it_generates_successful_response_with_static_file() {
override_environment_variables_from_config(Some("/src/test/rws.config.toml"));
let request_host_header_name = "Host";
let request_host_header_value = "localhost:7777";
let request_method = METHOD.get;
let request_uri = "/static/test.txt";
let request_http_version = VERSION.http_1_1.to_string();
let host = Header {
name: request_host_header_name.to_string(),
value: request_host_header_value.to_string()
};
let headers = vec![host];
let request = Request {
method: request_method.to_string(),
request_uri: request_uri.to_string(),
http_version: request_http_version.to_string(),
headers,
body: vec![],
};
let raw_request = Request::_generate_request(request);
let request: Request = Request::parse_request(&raw_request.as_bytes()).unwrap();
let host_header = request.get_header(request_host_header_name.to_string()).unwrap();
assert_eq!(request_host_header_value.to_string(), host_header.value);
assert_eq!(request_method.to_string(), request.method);
assert_eq!(request_uri.to_string(), request.request_uri);
assert_eq!(request_http_version.to_string(), request.http_version);
let response_http_version = VERSION.http_1_1.to_string();
let response_status_code = STATUS_CODE_REASON_PHRASE.n200_ok.status_code;
let response_reason_phrase = STATUS_CODE_REASON_PHRASE.n200_ok.reason_phrase;
let dir = env::current_dir().unwrap();
let working_directory = dir.as_path().to_str().unwrap();
let response_filepath = [working_directory, request.request_uri.as_str()].join(SYMBOL.empty_string);
let response_html_file= fs::read_to_string(response_filepath.to_string()).unwrap();
let response_content_length_header_name = "Content-Length";
let response_content_length_header_value = response_html_file.len().to_string();
let mock_tcp_stream = MockTcpStream {
read_data: raw_request.as_bytes().to_vec(),
write_data: vec![],
};
let peer_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0,0,0,0)), 0);
let raw_response: Vec<u8> = Server::process_request(mock_tcp_stream, peer_addr);
let response = Response::_parse_response(raw_response.borrow());
let header = response._get_header(response_content_length_header_name.to_string()).unwrap();
let content_type_header = response._get_header(Header::_CONTENT_TYPE.to_string()).unwrap();
let x_content_type_options_header = response._get_header(Header::_X_CONTENT_TYPE_OPTIONS.to_string()).unwrap();
assert_eq!(Header::_X_CONTENT_TYPE_OPTIONS_VALUE_NOSNIFF, x_content_type_options_header.value);
assert_eq!(MimeType::TEXT_PLAIN, content_type_header.value);
assert_eq!(response_content_length_header_value, header.value);
assert_eq!(response_http_version, response.http_version);
assert_eq!(*response_status_code, response.status_code);
assert_eq!(response_reason_phrase, response.reason_phrase);
assert_eq!(response_html_file.into_bytes(), response.content_range_list.get(0).unwrap().body);
}
#[test]
fn it_generates_not_found_page_for_absent_static_file() {
let request_host_header_name = "Host";
let request_host_header_value = "localhost:7777";
let request_method = METHOD.get;
let request_uri = "/static/nonexistingfile";
let request_http_version = VERSION.http_1_1.to_string();
let host = Header {
name: request_host_header_name.to_string(),
value: request_host_header_value.to_string()
};
let headers = vec![host];
let request = Request {
method: request_method.to_string(),
request_uri: request_uri.to_string(),
http_version: request_http_version.to_string(),
headers,
body: vec![],
};
let raw_request = Request::_generate_request(request);
let request: Request = Request::parse_request(&raw_request.as_bytes()).unwrap();
let host_header = request.get_header(request_host_header_name.to_string()).unwrap();
assert_eq!(request_host_header_value.to_string(), host_header.value);
assert_eq!(request_method.to_string(), request.method);
assert_eq!(request_uri.to_string(), request.request_uri);
assert_eq!(request_http_version.to_string(), request.http_version);
let response_http_version = VERSION.http_1_1;
let response_status_code = STATUS_CODE_REASON_PHRASE.n404_not_found.status_code;
let response_reason_phrase = STATUS_CODE_REASON_PHRASE.n404_not_found.reason_phrase;
let dir = env::current_dir().unwrap();
let working_directory = dir.as_path().to_str().unwrap();
let not_found_page_path = "404.html";
let response_filepath = [working_directory, SYMBOL.slash, not_found_page_path].join(SYMBOL.empty_string);
let response_html_file= fs::read_to_string(response_filepath.to_string()).unwrap();
let response_content_length_header_name = "Content-Length";
let response_content_length_header_value = response_html_file.len().to_string();
let mock_tcp_stream = MockTcpStream {
read_data: raw_request.as_bytes().to_vec(),
write_data: vec![],
};
let peer_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0,0,0,0)), 0);
let raw_response: Vec<u8> = Server::process_request(mock_tcp_stream, peer_addr);
let response = Response::_parse_response(raw_response.borrow());
let header = response._get_header(response_content_length_header_name.to_string()).unwrap();
let content_type_header = response._get_header(Header::_CONTENT_TYPE.to_string()).unwrap();
let x_content_type_options_header = response._get_header(Header::_X_CONTENT_TYPE_OPTIONS.to_string()).unwrap();
assert_eq!(Header::_X_CONTENT_TYPE_OPTIONS_VALUE_NOSNIFF, x_content_type_options_header.value);
assert_eq!(MimeType::TEXT_HTML, content_type_header.value);
assert_eq!(response_content_length_header_value, header.value);
assert_eq!(response_http_version, response.http_version);
assert_eq!(*response_status_code, response.status_code);
assert_eq!(response_reason_phrase, response.reason_phrase);
assert_eq!(response_html_file.as_bytes().to_vec(), response.content_range_list.get(0).unwrap().body);
}
#[test]
fn it_generates_not_found_page_for_absent_route() {
let request_host_header_name = "Host";
let request_host_header_value = "localhost:7777";
let request_method = METHOD.get;
let request_uri = "/nonexistingroute";
let request_http_version = VERSION.http_1_1.to_string();
let host = Header {
name: request_host_header_name.to_string(),
value: request_host_header_value.to_string()
};
let headers = vec![host];
let request = Request {
method: request_method.to_string(),
request_uri: request_uri.to_string(),
http_version: request_http_version.to_string(),
headers,
body: vec![],
};
let raw_request = Request::_generate_request(request);
let request: Request = Request::parse_request(&raw_request.as_bytes()).unwrap();
let host_header = request.get_header(request_host_header_name.to_string()).unwrap();
assert_eq!(request_host_header_value.to_string(), host_header.value);
assert_eq!(request_method.to_string(), request.method);
assert_eq!(request_uri.to_string(), request.request_uri);
assert_eq!(request_http_version.to_string(), request.http_version);
let response_http_version = VERSION.http_1_1.to_string();
let response_status_code = STATUS_CODE_REASON_PHRASE.n404_not_found.status_code;
let response_reason_phrase = STATUS_CODE_REASON_PHRASE.n404_not_found.reason_phrase;
let dir = env::current_dir().unwrap();
let working_directory = dir.as_path().to_str().unwrap();
let not_found_page_path = "404.html";
let response_filepath = [working_directory, SYMBOL.slash, not_found_page_path].join(SYMBOL.empty_string);
let response_html_file= fs::read_to_string(response_filepath.to_string()).unwrap();
let response_content_length_header_name = "Content-Length";
let response_content_length_header_value = response_html_file.len().to_string();
let mock_tcp_stream = MockTcpStream {
read_data: raw_request.as_bytes().to_vec(),
write_data: vec![],
};
let peer_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0,0,0,0)), 0);
let raw_response: Vec<u8> = Server::process_request(mock_tcp_stream, peer_addr);
let response = Response::_parse_response(raw_response.borrow());
let header = response._get_header(response_content_length_header_name.to_string()).unwrap();
let content_type_header = response._get_header(Header::_CONTENT_TYPE.to_string()).unwrap();
let x_content_type_options_header = response._get_header(Header::_X_CONTENT_TYPE_OPTIONS.to_string()).unwrap();
assert_eq!(Header::_X_CONTENT_TYPE_OPTIONS_VALUE_NOSNIFF, x_content_type_options_header.value);
assert_eq!(MimeType::TEXT_HTML, content_type_header.value);
assert_eq!(response_content_length_header_value, header.value);
assert_eq!(response_http_version, response.http_version);
assert_eq!(*response_status_code, response.status_code);
assert_eq!(response_reason_phrase, response.reason_phrase);
assert_eq!(response_html_file.into_bytes(), response.content_range_list.get(0).unwrap().body);
}
#[test]
fn it_generates_not_found_page_for_static_directory() {
let request_host_header_name = "Host";
let request_host_header_value = "localhost:7777";
let request_method = METHOD.get;
let request_uri = "/static/subdir/";
let request_http_version = VERSION.http_1_1.to_string();
let host = Header {
name: request_host_header_name.to_string(),
value: request_host_header_value.to_string()
};
let headers = vec![host];
let request = Request {
method: request_method.to_string(),
request_uri: request_uri.to_string(),
http_version: request_http_version.to_string(),
headers,
body: vec![],
};
let raw_request = Request::_generate_request(request);
let request: Request = Request::parse_request(&raw_request.as_bytes()).unwrap();
let host_header = request.get_header(request_host_header_name.to_string()).unwrap();
assert_eq!(request_host_header_value.to_string(), host_header.value);
assert_eq!(request_method.to_string(), request.method);
assert_eq!(request_uri.to_string(), request.request_uri);
assert_eq!(request_http_version.to_string(), request.http_version);
let response_http_version = VERSION.http_1_1.to_string();
let response_status_code = STATUS_CODE_REASON_PHRASE.n404_not_found.status_code;
let response_reason_phrase = STATUS_CODE_REASON_PHRASE.n404_not_found.reason_phrase;
let dir = env::current_dir().unwrap();
let working_directory = dir.as_path().to_str().unwrap();
let not_found_page_path = "404.html";
let response_filepath = [working_directory, SYMBOL.slash, not_found_page_path].join(SYMBOL.empty_string);
let response_html_file= fs::read_to_string(response_filepath.to_string()).unwrap();
let response_content_length_header_name = "Content-Length";
let response_content_length_header_value = response_html_file.len().to_string();
let mock_tcp_stream = MockTcpStream {
read_data: raw_request.as_bytes().to_vec(),
write_data: vec![],
};
let peer_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0,0,0,0)), 0);
let raw_response: Vec<u8> = Server::process_request(mock_tcp_stream, peer_addr);
let response = Response::_parse_response(raw_response.borrow());
let header = response._get_header(response_content_length_header_name.to_string()).unwrap();
let content_type_header = response._get_header(Header::_CONTENT_TYPE.to_string()).unwrap();
let x_content_type_options_header = response._get_header(Header::_X_CONTENT_TYPE_OPTIONS.to_string()).unwrap();
assert_eq!(Header::_X_CONTENT_TYPE_OPTIONS_VALUE_NOSNIFF, x_content_type_options_header.value);
assert_eq!(MimeType::TEXT_HTML, content_type_header.value);
assert_eq!(response_content_length_header_value, header.value);
assert_eq!(response_http_version, response.http_version);
assert_eq!(*response_status_code, response.status_code);
assert_eq!(response_reason_phrase, response.reason_phrase);
assert_eq!(response_html_file.into_bytes(), response.content_range_list.get(0).unwrap().body);
}
#[test]
fn it_generates_not_found_page_for_static_subdirectory() {
let request_host_header_name = "Host";
let request_host_header_value = "localhost:7777";
let request_method = METHOD.get;
let request_uri = "/static/subdir/";
let request_http_version = VERSION.http_1_1.to_string();
let host = Header {
name: request_host_header_name.to_string(),
value: request_host_header_value.to_string()
};
let headers = vec![host];
let request = Request {
method: request_method.to_string(),
request_uri: request_uri.to_string(),
http_version: request_http_version.to_string(),
headers,
body: vec![],
};
let raw_request = Request::_generate_request(request);
let request: Request = Request::parse_request(&raw_request.as_bytes()).unwrap();
let host_header = request.get_header(request_host_header_name.to_string()).unwrap();
assert_eq!(request_host_header_value.to_string(), host_header.value);
assert_eq!(request_method.to_string(), request.method);
assert_eq!(request_uri.to_string(), request.request_uri);
assert_eq!(request_http_version.to_string(), request.http_version);
let response_http_version = VERSION.http_1_1.to_string();
let response_status_code = STATUS_CODE_REASON_PHRASE.n404_not_found.status_code;
let response_reason_phrase = STATUS_CODE_REASON_PHRASE.n404_not_found.reason_phrase;
let dir = env::current_dir().unwrap();
let working_directory = dir.as_path().to_str().unwrap();
let not_found_page_path = "404.html";
let response_filepath = [working_directory, SYMBOL.slash, not_found_page_path].join(SYMBOL.empty_string);
let response_html_file= fs::read_to_string(response_filepath.to_string()).unwrap();
let response_content_length_header_name = "Content-Length";
let response_content_length_header_value = response_html_file.len().to_string();
let mock_tcp_stream = MockTcpStream {
read_data: raw_request.as_bytes().to_vec(),
write_data: vec![],
};
let peer_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0,0,0,0)), 0);
let raw_response: Vec<u8> = Server::process_request(mock_tcp_stream, peer_addr);
let response = Response::_parse_response(raw_response.borrow());
let header = response._get_header(response_content_length_header_name.to_string()).unwrap();
let content_type_header = response._get_header(Header::_CONTENT_TYPE.to_string()).unwrap();
let x_content_type_options_header = response._get_header(Header::_X_CONTENT_TYPE_OPTIONS.to_string()).unwrap();
assert_eq!(Header::_X_CONTENT_TYPE_OPTIONS_VALUE_NOSNIFF, x_content_type_options_header.value);
assert_eq!(MimeType::TEXT_HTML, content_type_header.value);
assert_eq!(response_content_length_header_value, header.value);
assert_eq!(response_http_version, response.http_version);
assert_eq!(*response_status_code, response.status_code);
assert_eq!(response_reason_phrase, response.reason_phrase);
assert_eq!(response_html_file.into_bytes(), response.content_range_list.get(0).unwrap().body);
}
#[test]
fn it_generates_bad_request_for_non_ut8_char_in_request() {
let mut non_utf8_char: Vec<u8> = vec![255];
let method = METHOD.get.to_lowercase();
let request_uri = "/path";
let http_version = VERSION.http_1_1.to_lowercase();
let mut request_vec : Vec<u8> = vec![];
request_vec.append(&mut method.as_bytes().to_vec());
request_vec.append(&mut request_uri.as_bytes().to_vec());
request_vec.append(&mut non_utf8_char);
request_vec.append(&mut http_version.as_bytes().to_vec());
let mock_tcp_stream = MockTcpStream {
read_data: request_vec,
write_data: vec![],
};
let peer_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0,0,0,0)), 0);
let raw_response: Vec<u8> = Server::process_request(mock_tcp_stream, peer_addr);
let str = String::from_utf8(raw_response.clone()).unwrap();
println!("{}", str);
let response = Response::_parse_response(&raw_response);
assert_eq!(*STATUS_CODE_REASON_PHRASE.n400_bad_request.status_code, response.status_code);
assert_eq!(STATUS_CODE_REASON_PHRASE.n400_bad_request.reason_phrase, response.reason_phrase);
assert_eq!(VERSION.http_1_1, response.http_version);
let content_type_header = response._get_header(Header::_CONTENT_TYPE.to_string()).unwrap();
assert_eq!(MimeType::TEXT_PLAIN, content_type_header.value);
let response_body = response.content_range_list.get(0).unwrap();
assert_eq!("invalid utf-8 sequence of 1 bytes from index 8", String::from_utf8(response_body.clone().body).unwrap());
}
#[test]
fn it_generates_successful_response_with_static_file_in_subdirectory() {
let request_host_header_name = "Host";
let request_host_header_value = "localhost:7777";
let request_method = METHOD.get;
let request_uri = "/static/test.txt";
let request_http_version = VERSION.http_1_1.to_string();
let host = Header {
name: request_host_header_name.to_string(),
value: request_host_header_value.to_string()
};
let headers = vec![host];
let request = Request {
method: request_method.to_string(),
request_uri: request_uri.to_string(),
http_version: request_http_version.to_string(),
headers,
body: vec![],
};
let raw_request = Request::_generate_request(request);
let request: Request = Request::parse_request(&raw_request.as_bytes()).unwrap();
let host_header = request.get_header(request_host_header_name.to_string()).unwrap();
assert_eq!(request_host_header_value.to_string(), host_header.value);
assert_eq!(request_method.to_string(), request.method);
assert_eq!(request_uri.to_string(), request.request_uri);
assert_eq!(request_http_version.to_string(), request.http_version);
let response_http_version = VERSION.http_1_1.to_string();
let response_status_code = STATUS_CODE_REASON_PHRASE.n200_ok.status_code;
let response_reason_phrase = STATUS_CODE_REASON_PHRASE.n200_ok.reason_phrase;
let dir = env::current_dir().unwrap();
let working_directory = dir.as_path().to_str().unwrap();
let response_filepath = [working_directory, request.request_uri.as_str()].join(SYMBOL.empty_string);
let response_html_file= fs::read_to_string(response_filepath.to_string()).unwrap();
let response_content_length_header_name = "Content-Length";
let response_content_length_header_value = response_html_file.len().to_string();
let mock_tcp_stream = MockTcpStream {
read_data: raw_request.as_bytes().to_vec(),
write_data: vec![],
};
let peer_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0,0,0,0)), 0);
let raw_response: Vec<u8> = Server::process_request(mock_tcp_stream, peer_addr);
let response = Response::_parse_response(raw_response.borrow());
let header = response._get_header(response_content_length_header_name.to_string()).unwrap();
let content_type_header = response._get_header(Header::_CONTENT_TYPE.to_string()).unwrap();
let x_content_type_options_header = response._get_header(Header::_X_CONTENT_TYPE_OPTIONS.to_string()).unwrap();
assert_eq!(Header::_X_CONTENT_TYPE_OPTIONS_VALUE_NOSNIFF, x_content_type_options_header.value);
assert_eq!(MimeType::TEXT_PLAIN, content_type_header.value);
assert_eq!(response_content_length_header_value, header.value);
assert_eq!(response_http_version, response.http_version);
assert_eq!(*response_status_code, response.status_code);
assert_eq!(response_reason_phrase, response.reason_phrase);
assert_eq!(response_html_file.into_bytes(), response.content_range_list.get(0).unwrap().body);
}
#[test]
fn it_generates_successful_response_with_static_file_in_subdirectory_to_head_request() {
let request_host_header_name = "Host";
let request_host_header_value = "localhost:7777";
let request_method = METHOD.head;
let request_uri = "/static/test.txt";
let request_http_version = VERSION.http_1_1.to_string();
let host = Header {
name: request_host_header_name.to_string(),
value: request_host_header_value.to_string()
};
let headers = vec![host];
let request = Request {
method: request_method.to_string(),
request_uri: request_uri.to_string(),
http_version: request_http_version.to_string(),
headers,
body: vec![],
};
let raw_request = Request::_generate_request(request);
let request: Request = Request::parse_request(&raw_request.as_bytes()).unwrap();
let host_header = request.get_header(request_host_header_name.to_string()).unwrap();
assert_eq!(request_host_header_value.to_string(), host_header.value);
assert_eq!(request_method.to_string(), request.method);
assert_eq!(request_uri.to_string(), request.request_uri);
assert_eq!(request_http_version.to_string(), request.http_version);
let response_http_version = VERSION.http_1_1.to_string();
let response_status_code = STATUS_CODE_REASON_PHRASE.n200_ok.status_code;
let response_reason_phrase = STATUS_CODE_REASON_PHRASE.n200_ok.reason_phrase;
let dir = env::current_dir().unwrap();
let working_directory = dir.as_path().to_str().unwrap();
let response_filepath = [working_directory, request.request_uri.as_str()].join(SYMBOL.empty_string);
let response_html_file= fs::read_to_string(response_filepath.to_string()).unwrap();
let response_content_length_header_name = "Content-Length";
let response_content_length_header_value = response_html_file.len().to_string();
let mock_tcp_stream = MockTcpStream {
read_data: raw_request.as_bytes().to_vec(),
write_data: vec![],
};
let peer_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0,0,0,0)), 0);
let raw_response: Vec<u8> = Server::process_request(mock_tcp_stream, peer_addr);
let response = Response::_parse_response(raw_response.borrow());
let header = response._get_header(response_content_length_header_name.to_string()).unwrap();
let content_type_header = response._get_header(Header::_CONTENT_TYPE.to_string()).unwrap();
let x_content_type_options_header = response._get_header(Header::_X_CONTENT_TYPE_OPTIONS.to_string()).unwrap();
assert_eq!(Header::_X_CONTENT_TYPE_OPTIONS_VALUE_NOSNIFF, x_content_type_options_header.value);
assert_eq!(MimeType::TEXT_PLAIN, content_type_header.value);
assert_eq!(response_content_length_header_value, header.value);
assert_eq!(response_http_version, response.http_version);
assert_eq!(*response_status_code, response.status_code);
assert_eq!(response_reason_phrase, response.reason_phrase);
assert_eq!(0, response.content_range_list.get(0).unwrap().range.end);
assert_eq!("0", response.content_range_list.get(0).unwrap().size);
}
#[test]
fn it_generates_successful_response_with_static_file_in_multiple_static_directories() {
let request_host_header_name = "Host";
let request_host_header_value = "localhost:7777";
let request_method = METHOD.get;
let request_uri = "/static/test.txt";
let request_http_version = VERSION.http_1_1.to_string();
let host = Header {
name: request_host_header_name.to_string(),
value: request_host_header_value.to_string()
};
let headers = vec![host];
let request = Request {
method: request_method.to_string(),
request_uri: request_uri.to_string(),
http_version: request_http_version.to_string(),
headers,
body: vec![],
};
let raw_request = Request::_generate_request(request);
let request: Request = Request::parse_request(&raw_request.as_bytes()).unwrap();
let host_header = request.get_header(request_host_header_name.to_string()).unwrap();
assert_eq!(request_host_header_value.to_string(), host_header.value);
assert_eq!(request_method.to_string(), request.method);
assert_eq!(request_uri.to_string(), request.request_uri);
assert_eq!(request_http_version.to_string(), request.http_version);
let response_http_version = VERSION.http_1_1.to_string();
let response_status_code = STATUS_CODE_REASON_PHRASE.n200_ok.status_code;
let response_reason_phrase = STATUS_CODE_REASON_PHRASE.n200_ok.reason_phrase;
let dir = env::current_dir().unwrap();
let working_directory = dir.as_path().to_str().unwrap();
let response_filepath = [working_directory, request.request_uri.as_str()].join(SYMBOL.empty_string);
let response_html_file= fs::read_to_string(response_filepath.to_string()).unwrap();
let response_content_length_header_name = "Content-Length";
let response_content_length_header_value = response_html_file.len().to_string();
let mock_tcp_stream = MockTcpStream {
read_data: raw_request.as_bytes().to_vec(),
write_data: vec![],
};
let peer_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0,0,0,0)), 0);
let raw_response: Vec<u8> = Server::process_request(mock_tcp_stream, peer_addr);
let response = Response::_parse_response(raw_response.borrow());
let header = response._get_header(response_content_length_header_name.to_string()).unwrap();
let content_type_header = response._get_header(Header::_CONTENT_TYPE.to_string()).unwrap();
let x_content_type_options_header = response._get_header(Header::_X_CONTENT_TYPE_OPTIONS.to_string()).unwrap();
assert_eq!(Header::_X_CONTENT_TYPE_OPTIONS_VALUE_NOSNIFF, x_content_type_options_header.value);
assert_eq!(MimeType::TEXT_PLAIN, content_type_header.value);
assert_eq!(response_content_length_header_value, header.value);
assert_eq!(response_http_version, response.http_version);
assert_eq!(*response_status_code, response.status_code);
assert_eq!(response_reason_phrase, response.reason_phrase);
assert_eq!(response_html_file.into_bytes(), response.content_range_list.get(0).unwrap().body);
let request_host_header_name = "Host";
let request_host_header_value = "localhost:7777";
let request_method = METHOD.get;
let request_uri = "/assets/test.txt";
let request_http_version = VERSION.http_1_1.to_string();
let host = Header {
name: request_host_header_name.to_string(),
value: request_host_header_value.to_string()
};
let headers = vec![host];
let request = Request {
method: request_method.to_string(),
request_uri: request_uri.to_string(),
http_version: request_http_version.to_string(),
headers,
body: vec![],
};
let raw_request = Request::_generate_request(request);
let request: Request = Request::parse_request(&raw_request.as_bytes()).unwrap();
let host_header = request.get_header(request_host_header_name.to_string()).unwrap();
assert_eq!(request_host_header_value.to_string(), host_header.value);
assert_eq!(request_method.to_string(), request.method);
assert_eq!(request_uri.to_string(), request.request_uri);
assert_eq!(request_http_version.to_string(), request.http_version);
let response_http_version = VERSION.http_1_1.to_string();
let response_status_code = STATUS_CODE_REASON_PHRASE.n200_ok.status_code;
let response_reason_phrase = STATUS_CODE_REASON_PHRASE.n200_ok.reason_phrase;
let dir = env::current_dir().unwrap();
let working_directory = dir.as_path().to_str().unwrap();
let response_filepath = [working_directory, request.request_uri.as_str()].join(SYMBOL.empty_string);
let response_html_file= fs::read_to_string(response_filepath.to_string()).unwrap();
let response_content_length_header_name = "Content-Length";
let response_content_length_header_value = response_html_file.len().to_string();
let mock_tcp_stream = MockTcpStream {
read_data: raw_request.as_bytes().to_vec(),
write_data: vec![],
};
let peer_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0,0,0,0)), 0);
let raw_response: Vec<u8> = Server::process_request(mock_tcp_stream, peer_addr);
let response = Response::_parse_response(raw_response.borrow());
let header = response._get_header(response_content_length_header_name.to_string()).unwrap();
let content_type_header = response._get_header(Header::_CONTENT_TYPE.to_string()).unwrap();
let x_content_type_options_header = response._get_header(Header::_X_CONTENT_TYPE_OPTIONS.to_string()).unwrap();
assert_eq!(Header::_X_CONTENT_TYPE_OPTIONS_VALUE_NOSNIFF, x_content_type_options_header.value);
assert_eq!(MimeType::TEXT_PLAIN, content_type_header.value);
assert_eq!(response_content_length_header_value, header.value);
assert_eq!(response_http_version, response.http_version);
assert_eq!(*response_status_code, response.status_code);
assert_eq!(response_reason_phrase, response.reason_phrase);
assert_eq!(response_html_file.into_bytes(), response.content_range_list.get(0).unwrap().body);
}
#[test]
fn check_range_response_for_not_proper_range_header() {
let uri = "/static/test.txt";
let url = FileExt::get_static_filepath(uri).unwrap();
let file = File::open(url).unwrap();
let mut reader = BufReader::new(file);
let mut buffer = Vec::new();
reader.read_to_end(&mut buffer).unwrap();
let length = buffer.len();
let mid = length / 2;
let end_of_first_range = mid;
let start_of_second_range = mid + 1;
let not_proper_end_of_second_range = length + 10;
let range_header_value = format!("bytdgdes=0-{}, {}-{}", end_of_first_range, start_of_second_range, not_proper_end_of_second_range);
let request_host_header_name = "Host";
let request_host_header_value = "localhost:7777";
let host = Header {
name: request_host_header_name.to_string(),
value: request_host_header_value.to_string()
};
let range = Header {
name: Header::_RANGE.to_string(),
value: range_header_value.to_string()
};
let headers = vec![host, range];
let request = Request {
method: METHOD.get.to_string(),
request_uri: uri.to_string(),
http_version: VERSION.http_1_1.to_string(),
headers,
body: vec![],
};
let raw_request = Request::_generate_request(request);
let mock_tcp_stream = MockTcpStream {
read_data: raw_request.as_bytes().to_vec(),
write_data: vec![],
};
let peer_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0,0,0,0)), 0);
let raw_response: Vec<u8> = Server::process_request(mock_tcp_stream, peer_addr);
let response = Response::_parse_response(raw_response.borrow());
let response_string = String::from_utf8(raw_response).unwrap();
println!("\n\n\n{}", &raw_request);
println!("\n\n\n{}", &response_string);
assert_eq!(VERSION.http_1_1, response.http_version);
let header = response._get_header(Header::_X_CONTENT_TYPE_OPTIONS.to_string()).unwrap();
assert_eq!(Header::_X_CONTENT_TYPE_OPTIONS_VALUE_NOSNIFF, header.value);
let header = response._get_header(Header::_ACCEPT_RANGES.to_string()).unwrap();
assert_eq!(Range::BYTES, header.value);
assert_eq!(*STATUS_CODE_REASON_PHRASE.n416_range_not_satisfiable.status_code, response.status_code);
assert_eq!(STATUS_CODE_REASON_PHRASE.n416_range_not_satisfiable.reason_phrase, response.reason_phrase);
let content_range = response.content_range_list.get(0).unwrap();
assert_eq!(content_range.body, Range::ERROR_MALFORMED_RANGE_HEADER_WRONG_UNIT.as_bytes());
}
#[test]
fn check_range_response_for_not_proper_range_header_range_end_bigger_than_filesize() {
let uri = "/static/test.txt";
let url = FileExt::get_static_filepath(uri).unwrap();
let file = File::open(url).unwrap();
let mut reader = BufReader::new(file);
let mut buffer = Vec::new();
reader.read_to_end(&mut buffer).unwrap();
let length = buffer.len();
let mid = length / 2;
let end_of_first_range = mid;
let start_of_second_range = mid + 1;
let not_proper_end_of_second_range = length + 10;
let range_header_value = format!("bytes=0-{}, {}-{}", end_of_first_range, start_of_second_range, not_proper_end_of_second_range);
let request_host_header_name = "Host";
let request_host_header_value = "localhost:7777";
let host = Header {
name: request_host_header_name.to_string(),
value: request_host_header_value.to_string()
};
let range = Header {
name: Header::_RANGE.to_string(),
value: range_header_value.to_string()
};
let headers = vec![host, range];
let request = Request {
method: METHOD.get.to_string(),
request_uri: uri.to_string(),
http_version: VERSION.http_1_1.to_string(),
headers,
body: vec![],
};
let raw_request = Request::_generate_request(request);
let mock_tcp_stream = MockTcpStream {
read_data: raw_request.as_bytes().to_vec(),
write_data: vec![],
};
let peer_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0,0,0,0)), 0);
let raw_response: Vec<u8> = Server::process_request(mock_tcp_stream, peer_addr);
let response = Response::_parse_response(raw_response.borrow());
let response_string = String::from_utf8(raw_response).unwrap();
println!("\n\n\n{}", &raw_request);
println!("\n\n\n{}", &response_string);
assert_eq!(VERSION.http_1_1, response.http_version);
let header = response._get_header(Header::_X_CONTENT_TYPE_OPTIONS.to_string()).unwrap();
assert_eq!(Header::_X_CONTENT_TYPE_OPTIONS_VALUE_NOSNIFF, header.value);
let header = response._get_header(Header::_ACCEPT_RANGES.to_string()).unwrap();
assert_eq!(Range::BYTES, header.value);
assert_eq!(*STATUS_CODE_REASON_PHRASE.n416_range_not_satisfiable.status_code, response.status_code);
assert_eq!(STATUS_CODE_REASON_PHRASE.n416_range_not_satisfiable.reason_phrase, response.reason_phrase);
let content_range = response.content_range_list.get(0).unwrap();
assert_eq!(content_range.body, Range::ERROR_END_IS_BIGGER_THAN_FILESIZE_CONTENT_RANGE.as_bytes());
}
#[test]
fn check_range_response_for_not_proper_range_header_range_start_bigger_than_end() {
let uri = "/static/test.txt";
let url = FileExt::get_static_filepath(uri).unwrap();
let file = File::open(url).unwrap();
let mut reader = BufReader::new(file);
let mut buffer = Vec::new();
reader.read_to_end(&mut buffer).unwrap();
let length = buffer.len();
let mid = length / 2;
let end_of_first_range = mid;
let start_of_second_range = length;
let not_proper_end_of_second_range = mid;
let range_header_value = format!("bytes=0-{}, {}-{}", end_of_first_range, start_of_second_range, not_proper_end_of_second_range);
let request_host_header_name = "Host";
let request_host_header_value = "localhost:7777";
let host = Header {
name: request_host_header_name.to_string(),
value: request_host_header_value.to_string()
};
let range = Header {
name: Header::_RANGE.to_string(),
value: range_header_value.to_string()
};
let headers = vec![host, range];
let request = Request {
method: METHOD.get.to_string(),
request_uri: uri.to_string(),
http_version: VERSION.http_1_1.to_string(),
headers,
body: vec![],
};
let raw_request = Request::_generate_request(request);
let mock_tcp_stream = MockTcpStream {
read_data: raw_request.as_bytes().to_vec(),
write_data: vec![],
};
let peer_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0,0,0,0)), 0);
let raw_response: Vec<u8> = Server::process_request(mock_tcp_stream, peer_addr);
let response = Response::_parse_response(raw_response.borrow());
let response_string = String::from_utf8(raw_response).unwrap();
println!("\n\n\n{}", &raw_request);
println!("\n\n\n{}", &response_string);
assert_eq!(VERSION.http_1_1, response.http_version);
let header = response._get_header(Header::_X_CONTENT_TYPE_OPTIONS.to_string()).unwrap();
assert_eq!(Header::_X_CONTENT_TYPE_OPTIONS_VALUE_NOSNIFF, header.value);
let header = response._get_header(Header::_ACCEPT_RANGES.to_string()).unwrap();
assert_eq!(Range::BYTES, header.value);
assert_eq!(*STATUS_CODE_REASON_PHRASE.n416_range_not_satisfiable.status_code, response.status_code);
assert_eq!(STATUS_CODE_REASON_PHRASE.n416_range_not_satisfiable.reason_phrase, response.reason_phrase);
let content_range = response.content_range_list.get(0).unwrap();
assert_eq!(content_range.body, Range::ERROR_START_IS_AFTER_END_CONTENT_RANGE.as_bytes());
}
#[test]
fn check_range_response_for_not_proper_range_header_range_start_malformed() {
let uri = "/static/test.txt";
let url = FileExt::get_static_filepath(uri).unwrap();
let file = File::open(url).unwrap();
let mut reader = BufReader::new(file);
let mut buffer = Vec::new();
reader.read_to_end(&mut buffer).unwrap();
let length = buffer.len();
let mid = length / 2;
let end_of_first_range = mid;
let start_of_second_range = length + 10;
let not_proper_end_of_second_range = mid;
let range_header_value = format!("bytes=0-{}, {}zaksd-{}", end_of_first_range, start_of_second_range, not_proper_end_of_second_range);
let request_host_header_name = "Host";
let request_host_header_value = "localhost:7777";
let host = Header {
name: request_host_header_name.to_string(),
value: request_host_header_value.to_string()
};
let range = Header {
name: Header::_RANGE.to_string(),
value: range_header_value.to_string()
};
let headers = vec![host, range];
let request = Request {
method: METHOD.get.to_string(),
request_uri: uri.to_string(),
http_version: VERSION.http_1_1.to_string(),
headers,
body: vec![],
};
let raw_request = Request::_generate_request(request);
let mock_tcp_stream = MockTcpStream {
read_data: raw_request.as_bytes().to_vec(),
write_data: vec![],
};
let peer_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0,0,0,0)), 0);
let raw_response: Vec<u8> = Server::process_request(mock_tcp_stream, peer_addr);
let response = Response::_parse_response(raw_response.borrow());
let response_string = String::from_utf8(raw_response).unwrap();
println!("\n\n\n{}", &raw_request);
println!("\n\n\n{}", &response_string);
assert_eq!(VERSION.http_1_1, response.http_version);
let header = response._get_header(Header::_X_CONTENT_TYPE_OPTIONS.to_string()).unwrap();
assert_eq!(Header::_X_CONTENT_TYPE_OPTIONS_VALUE_NOSNIFF, header.value);
let header = response._get_header(Header::_ACCEPT_RANGES.to_string()).unwrap();
assert_eq!(Range::BYTES, header.value);
assert_eq!(*STATUS_CODE_REASON_PHRASE.n416_range_not_satisfiable.status_code, response.status_code);
assert_eq!(STATUS_CODE_REASON_PHRASE.n416_range_not_satisfiable.reason_phrase, response.reason_phrase);
let content_range = response.content_range_list.get(0).unwrap();
assert_eq!(content_range.body, Range::ERROR_UNABLE_TO_PARSE_RANGE_START.as_bytes());
}
#[test]
fn check_range_response_for_not_proper_range_header_range_end_malformed() {
let uri = "/static/test.txt";
let url = FileExt::get_static_filepath(uri).unwrap();
let file = File::open(url).unwrap();
let mut reader = BufReader::new(file);
let mut buffer = Vec::new();
reader.read_to_end(&mut buffer).unwrap();
let length = buffer.len();
let mid = length / 2;
let end_of_first_range = mid;
let start_of_second_range = length + 10;
let not_proper_end_of_second_range = mid;
let range_header_value = format!("bytes=0-{}zaksd, {}-{}", end_of_first_range, start_of_second_range, not_proper_end_of_second_range);
let request_host_header_name = "Host";
let request_host_header_value = "localhost:7777";
let host = Header {
name: request_host_header_name.to_string(),
value: request_host_header_value.to_string()
};
let range = Header {
name: Header::_RANGE.to_string(),
value: range_header_value.to_string()
};
let headers = vec![host, range];
let request = Request {
method: METHOD.get.to_string(),
request_uri: uri.to_string(),
http_version: VERSION.http_1_1.to_string(),
headers,
body: vec![],
};
let raw_request = Request::_generate_request(request);
let mock_tcp_stream = MockTcpStream {
read_data: raw_request.as_bytes().to_vec(),
write_data: vec![],
};
let peer_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0,0,0,0)), 0);
let raw_response: Vec<u8> = Server::process_request(mock_tcp_stream, peer_addr);
let response = Response::_parse_response(raw_response.borrow());
let response_string = String::from_utf8(raw_response).unwrap();
println!("\n\n\n{}", &raw_request);
println!("\n\n\n{}", &response_string);
assert_eq!(VERSION.http_1_1, response.http_version);
let header = response._get_header(Header::_X_CONTENT_TYPE_OPTIONS.to_string()).unwrap();
assert_eq!(Header::_X_CONTENT_TYPE_OPTIONS_VALUE_NOSNIFF, header.value);
let header = response._get_header(Header::_ACCEPT_RANGES.to_string()).unwrap();
assert_eq!(Range::BYTES, header.value);
assert_eq!(*STATUS_CODE_REASON_PHRASE.n416_range_not_satisfiable.status_code, response.status_code);
assert_eq!(STATUS_CODE_REASON_PHRASE.n416_range_not_satisfiable.reason_phrase, response.reason_phrase);
let content_range = response.content_range_list.get(0).unwrap();
assert_eq!(content_range.body, Range::ERROR_UNABLE_TO_PARSE_RANGE_END.as_bytes());
}
#[test]
fn check_range_response_for_not_proper_range_header_malformed() {
let uri = "/static/test.txt";
let url = FileExt::get_static_filepath(uri).unwrap();
let file = File::open(url).unwrap();
let mut reader = BufReader::new(file);
let mut buffer = Vec::new();
reader.read_to_end(&mut buffer).unwrap();
let range_header_value = format!("bytes=zaksd");
let request_host_header_name = "Host";
let request_host_header_value = "localhost:7777";
let host = Header {
name: request_host_header_name.to_string(),
value: request_host_header_value.to_string()
};
let range = Header {
name: Header::_RANGE.to_string(),
value: range_header_value.to_string()
};
let headers = vec![host, range];
let request = Request {
method: METHOD.get.to_string(),
request_uri: uri.to_string(),
http_version: VERSION.http_1_1.to_string(),
headers,
body: vec![],
};
let raw_request = Request::_generate_request(request);
let mock_tcp_stream = MockTcpStream {
read_data: raw_request.as_bytes().to_vec(),
write_data: vec![],
};
let peer_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0,0,0,0)), 0);
let raw_response: Vec<u8> = Server::process_request(mock_tcp_stream, peer_addr);
let response = Response::_parse_response(raw_response.borrow());
let response_string = String::from_utf8(raw_response).unwrap();
println!("\n\n\n{}", &raw_request);
println!("\n\n\n{}", &response_string);
assert_eq!(VERSION.http_1_1, response.http_version);
let header = response._get_header(Header::_X_CONTENT_TYPE_OPTIONS.to_string()).unwrap();
assert_eq!(Header::_X_CONTENT_TYPE_OPTIONS_VALUE_NOSNIFF, header.value);
let header = response._get_header(Header::_ACCEPT_RANGES.to_string()).unwrap();
assert_eq!(Range::BYTES, header.value);
assert_eq!(*STATUS_CODE_REASON_PHRASE.n416_range_not_satisfiable.status_code, response.status_code);
assert_eq!(STATUS_CODE_REASON_PHRASE.n416_range_not_satisfiable.reason_phrase, response.reason_phrase);
let content_range = response.content_range_list.get(0).unwrap();
assert_eq!(content_range.body, Range::ERROR_UNABLE_TO_PARSE_RANGE_START.as_bytes());
}
struct SequentialMockStream {
requests: Vec<Vec<u8>>,
read_index: usize,
written: Vec<u8>,
}
impl SequentialMockStream {
fn new(requests: Vec<Vec<u8>>) -> Self {
SequentialMockStream { requests, read_index: 0, written: vec![] }
}
}
impl Read for SequentialMockStream {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
if self.read_index >= self.requests.len() {
return Ok(0);
}
let data = &self.requests[self.read_index];
let n = data.len().min(buf.len());
buf[..n].copy_from_slice(&data[..n]);
self.read_index += 1;
Ok(n)
}
}
impl Write for SequentialMockStream {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.written.extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> { Ok(()) }
}
impl Unpin for SequentialMockStream {}
fn make_connection() -> crate::server::ConnectionInfo {
crate::server::ConnectionInfo {
client: crate::server::Address { ip: "127.0.0.1".to_string(), port: 12345 },
server: crate::server::Address { ip: "127.0.0.1".to_string(), port: 7878 },
request_size: 16000,
sni_hostname: None,
}
}
fn raw_get_request(uri: &str, http_version: &str, extra_headers: &[(&str, &str)]) -> Vec<u8> {
let mut req = format!("GET {} {}\r\nHost: localhost:7878\r\n", uri, http_version);
for (name, value) in extra_headers {
req.push_str(&format!("{}: {}\r\n", name, value));
}
req.push_str("\r\n");
req.into_bytes()
}
fn count_occurrences(haystack: &[u8], needle: &[u8]) -> usize {
haystack.windows(needle.len()).filter(|w| *w == needle).count()
}
#[test]
fn keep_alive_http11_serves_two_requests_on_same_stream() {
let req1 = raw_get_request("/static/test.txt", VERSION.http_1_1, &[]);
let req2 = raw_get_request("/static/test.txt", VERSION.http_1_1, &[]);
let mut stream = SequentialMockStream::new(vec![req1, req2]);
let app = crate::app::App::new();
Server::process(&mut stream, make_connection(), app).unwrap();
let written = String::from_utf8_lossy(&stream.written);
assert_eq!(
count_occurrences(&stream.written, b"HTTP/1.1 200"),
2,
"expected two 200 responses, got:\n{}",
written
);
assert!(
written.contains("Connection: keep-alive"),
"expected Connection: keep-alive in written output"
);
}
#[test]
fn connection_close_header_serves_one_request_and_stops() {
let req1 = raw_get_request("/static/test.txt", VERSION.http_1_1, &[("Connection", "close")]);
let req2 = raw_get_request("/static/test.txt", VERSION.http_1_1, &[]);
let mut stream = SequentialMockStream::new(vec![req1, req2]);
let app = crate::app::App::new();
Server::process(&mut stream, make_connection(), app).unwrap();
assert_eq!(
count_occurrences(&stream.written, b"HTTP/1.1 200"),
1,
"expected exactly one response after Connection: close"
);
let written = String::from_utf8_lossy(&stream.written);
assert!(written.contains("Connection: close"), "expected Connection: close in response");
}
#[test]
fn http10_defaults_to_connection_close() {
let req1 = raw_get_request("/static/test.txt", VERSION.http_1_0, &[]);
let req2 = raw_get_request("/static/test.txt", VERSION.http_1_0, &[]);
let mut stream = SequentialMockStream::new(vec![req1, req2]);
let app = crate::app::App::new();
Server::process(&mut stream, make_connection(), app).unwrap();
assert_eq!(
count_occurrences(&stream.written, b"HTTP/1.1 200"),
1,
"HTTP/1.0 should close after one response"
);
let written = String::from_utf8_lossy(&stream.written);
assert!(written.contains("Connection: close"));
}
#[test]
fn http11_explicit_keep_alive_serves_multiple_requests() {
let req1 = raw_get_request("/static/test.txt", VERSION.http_1_1, &[("Connection", "keep-alive")]);
let req2 = raw_get_request("/static/test.txt", VERSION.http_1_1, &[("Connection", "keep-alive")]);
let mut stream = SequentialMockStream::new(vec![req1, req2]);
let app = crate::app::App::new();
Server::process(&mut stream, make_connection(), app).unwrap();
assert_eq!(
count_occurrences(&stream.written, b"HTTP/1.1 200"),
2,
"explicit Connection: keep-alive should serve both requests"
);
}
#[test]
fn write_chunked_file_produces_valid_chunked_encoding() {
let dir = env::current_dir().unwrap();
let filepath = dir.join("target").join("_chunked_test_file.txt");
let content = b"hello chunked world";
std::fs::write(&filepath, content).unwrap();
let response = Response {
http_version: VERSION.http_1_1.to_string(),
status_code: *STATUS_CODE_REASON_PHRASE.n200_ok.status_code,
reason_phrase: STATUS_CODE_REASON_PHRASE.n200_ok.reason_phrase.to_string(),
headers: vec![Header {
name: Header::_CONTENT_TYPE.to_string(),
value: MimeType::TEXT_PLAIN.to_string(),
}],
content_range_list: vec![],
stream_file: None,
};
let request = Request {
method: METHOD.get.to_string(),
request_uri: "/".to_string(),
http_version: VERSION.http_1_1.to_string(),
headers: vec![],
body: vec![],
};
let mut out: Vec<u8> = vec![];
Server::write_chunked_file(&mut out, response, request, filepath.to_str().unwrap()).unwrap();
let raw = String::from_utf8_lossy(&out);
assert!(raw.contains("HTTP/1.1 200 OK"), "missing status line");
assert!(raw.contains("Transfer-Encoding: chunked"), "missing Transfer-Encoding header");
let hex_prefix = format!("{:x}\r\n", content.len());
assert!(
raw.contains(&hex_prefix),
"missing chunk size '{}' in:\n{}",
hex_prefix.trim(),
raw
);
assert!(raw.contains("hello chunked world"), "missing chunk body");
assert!(out.ends_with(b"0\r\n\r\n"), "missing terminal chunk");
std::fs::remove_file(&filepath).ok();
}
#[test]
fn connection_info_peer_addr_parses_ipv4() {
use crate::server::{Address, ConnectionInfo};
let info = ConnectionInfo {
client: Address { ip: "127.0.0.1".to_string(), port: 8080 },
server: Address { ip: "127.0.0.1".to_string(), port: 7878 },
request_size: 0,
sni_hostname: None,
};
let addr = info.peer_addr().unwrap();
assert_eq!("127.0.0.1:8080", addr.to_string());
}
#[test]
fn connection_info_peer_addr_returns_none_for_bad_ip() {
use crate::server::{Address, ConnectionInfo};
let info = ConnectionInfo {
client: Address { ip: "not-an-ip".to_string(), port: 80 },
server: Address { ip: "127.0.0.1".to_string(), port: 7878 },
request_size: 0,
sni_hostname: None,
};
assert!(info.peer_addr().is_none());
}
#[test]
fn address_to_socket_addr_parses_ipv6() {
use crate::server::Address;
let addr = Address { ip: "::1".to_string(), port: 443 };
let sa = addr.to_socket_addr().unwrap();
assert_eq!(443, sa.port());
}
#[test]
fn address_to_socket_addr_returns_none_for_negative_port() {
use crate::server::Address;
let addr = Address { ip: "127.0.0.1".to_string(), port: -1 };
assert!(addr.to_socket_addr().is_none());
}