extern crate chrono;
use std::string::String;
use std::fmt;
use self::chrono::Local;
pub struct Response {
kind: ResponseType,
headers: Vec<String>,
body: Option<String>,
file: Option<Vec<u8>>,
}
pub enum ResponseType {
NotFound,
HTTPOk,
#[allow(dead_code)]
Redirection,
ServerError,
}
impl Response {
fn new_text(body: String, headers: Option<Vec<String>>, kind: ResponseType) -> Response {
Response {
body: Some(body),
headers: headers.unwrap_or_default(),
kind,
file: None,
}
}
pub fn new_file(file: Vec<u8>, headers: Option<Vec<String>>, kind: ResponseType)-> Response {
Response {
body: None,
headers: headers.unwrap_or_default(),
kind,
file: Some(file),
}
}
#[allow(dead_code)]
pub fn http_ok(body: String) -> Response {
let mut response = Response::new_text(body, None, ResponseType::HTTPOk);
response.default_headers();
response
}
pub fn http_ok_file(file: Vec<u8>) -> Response {
let mut response = Response::new_file(file, None, ResponseType::HTTPOk);
response.default_headers();
response
}
pub fn not_found() -> Response {
let body = r#"<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Page Not Found</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
* {
line-height: 1.2;
margin: 0;
}
html {
color: #888;
display: table;
font-family: sans-serif;
height: 100%;
text-align: center;
width: 100%;
}
body {
display: table-cell;
vertical-align: middle;
margin: 2em auto;
}
h1 {
color: #555;
font-size: 2em;
font-weight: 400;
}
p {
margin: 0 auto;
width: 280px;
}
@media only screen and (max-width: 280px) {
body, p {
width: 95%;
}
h1 {
font-size: 1.5em;
margin: 0 0 0.3em;
}
}
</style>
</head>
<body>
<h1>Page Not Found</h1>
<p>Sorry, but the page you were trying to view does not exist.</p>
</body>
</html>
<!-- IE needs 512+ bytes: https://blogs.msdn.microsoft.com/ieinternals/2010/08/18/friendly-http-error-pages/ -->"#.to_string();
let mut response = Response::new_text(body, None, ResponseType::NotFound);
response.default_headers();
response
}
pub fn server_error() -> Response {
let mut r = Response {
body: Some(String::from("<html><body><h1>rust-http-server: Internal server error</h1></body></html>")),
headers: Vec::new(),
kind: ResponseType::ServerError,
file: None,
};
r.default_headers();
r
}
fn default_headers(&mut self) {
let status = format!("{} {} {}", self.http_version(), self.code(), self.kind);
let date = format!("Date: {}", Local::now().to_rfc2822());
let server = format!("Server: {}", self.server());
self.add_response_headers(&status);
self.add_response_headers(&date);
self.add_response_headers(&server);
}
pub fn add_response_headers(&mut self, header: &str) {
self.headers.push(header.to_string());
}
pub fn code(&self) -> u16 {
match self.kind {
ResponseType::HTTPOk => { 200 },
ResponseType::Redirection => { 300 },
ResponseType::NotFound => { 404 },
ResponseType::ServerError => { 500 },
}
}
pub fn http_version(&self) -> &str {
"HTTP/1.1"
}
pub fn server(&self) -> String {
String::from(env!("CARGO_PKG_NAME"))
}
pub fn to_string(&self) -> String {
let mut headers = self.headers_only();
let body_copy = self.body.clone();
headers.push_str(&body_copy.unwrap());
headers
}
fn headers_only(&self) -> String {
let mut result = self.headers.join("\r\n").to_string();
result.push_str("\r\n\r\n");
result
}
pub fn to_bytes(&self) -> Vec<u8> {
vprintln!("Converting response to binary");
let string_rep = self.headers_only();
let mut result = string_rep.into_bytes();
let mut body = self.file.clone().unwrap();
result.append(&mut body);
result
}
#[allow(dead_code)]
fn mime(&self) -> &str {
unimplemented!();
}
}
impl fmt::Display for ResponseType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
ResponseType::HTTPOk => write!(f, "{}", "OK"),
ResponseType::Redirection => write!(f, "{}", "Redirection"),
ResponseType::NotFound => write!(f, "{}", "Not Found"),
ResponseType::ServerError => write!(f, "{}", "Internal Server Error"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_response_structure() {
let status_line = "HTTP/1.1 200 OK";
let server_header = format!("Server: {}", env!("CARGO_PKG_NAME"));
let body = "This is the body";
let test_response = Response::http_ok(body.to_string());
let result = test_response.to_string();
let return_newline = result.matches("\r\n").count();
assert_eq!(4, return_newline);
let split_response: Vec<&str> = result.split("\r\n").collect();
assert_eq!(status_line, split_response[0]);
assert_eq!(server_header, split_response[2]);
assert_eq!(body, split_response[split_response.len() - 1]);
}
}