Http responses contain body
This commit is contained in:
@ -89,3 +89,66 @@ pub static HTTP_STATUSES: [(u16, StatusClass, &str, &str); 41] = [
|
||||
(505, ServerError, "HTTP Version Not Supported",
|
||||
"The server does not support, or refuses to support, the HTTP protocol version that was used in the request message."),
|
||||
];
|
||||
|
||||
pub static DEFAULT_DOCUMENT: &str = "\
|
||||
<!DOCTYPE html>\n\
|
||||
<html lang=\"en\">\n\
|
||||
<head>\n\
|
||||
\t<title>{status_code} {status_message} - Locutus - {hostname}</title>\n\
|
||||
\t<meta charset=\"UTF-8\"/>\n\
|
||||
\t<meta name=\"theme-color\" content=\"{theme_color}\"/>\n\
|
||||
\t<meta name=\"color-scheme\" content=\"light dark\"/>\n\
|
||||
\t<meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black-translucent\"/>\n\
|
||||
\t<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\"/>\n\
|
||||
\t<link rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/favicon.ico\"/>\n\
|
||||
\t<style>\n\
|
||||
\t\thtml{{font-family:\"Arial\",sans-serif;--error:#C00000;--warning:#E0C000;--success:#008000;--info:#606060;--color:var(--{color_name});}}\n\
|
||||
\t\tbody{{background-color:#F0F0F0;margin:0;}}\n\
|
||||
\t\tmain{{max-width:650px;margin:2em auto;}}\n\
|
||||
\t\tsection{{margin:1em;background-color:#FFFFFF;border: 1px solid var(--color);border-radius:4px;padding:1em;}}\n\
|
||||
\t\th1,h2,h3,h4,h5,h6,h7{{text-align:center;color:var(--color);font-weight:normal;}}\n\
|
||||
\t\th1{{font-size:3em;margin:0.125em 0 0.125em 0;}}\n\
|
||||
\t\th2{{font-size:1.5em;margin:0.25em 0 1em 0;}}\n\
|
||||
\t\tp{{text-align:center;font-size:0.875em;}}\n\
|
||||
\t\tdiv.footer{{color:#808080;font-size:0.75em;text-align:center;margin:2em 0 0.5em 0;}}\n\
|
||||
\t\tdiv.footer a{{color:#808080;}}\n\
|
||||
\t\t@media(prefers-color-scheme:dark){{\n\
|
||||
\t\t\thtml{{color:#FFFFFF;}}\n\
|
||||
\t\t\tbody{{background-color:#101010;}}\n\
|
||||
\t\t\tsection{{background-color:#181818;}}\n\
|
||||
\t\t}}\n\
|
||||
\t</style>\n\
|
||||
</head>\n\
|
||||
<body>\n\
|
||||
\t<main>\n\
|
||||
\t\t<section>\n\
|
||||
{doc}\
|
||||
\t\t\t<div class=\"footer\"><a href=\"https://{hostname}/\">{hostname}</a> - {server_str}</div>\n\
|
||||
\t\t</section>\n\
|
||||
\t</main>\n\
|
||||
</body>\n\
|
||||
</html>\n";
|
||||
|
||||
pub static ERROR_DOCUMENT: &str = "\
|
||||
\t\t\t<h1>{code}</h1>\n\
|
||||
\t\t\t<h2>{message} :(</h2>\n\
|
||||
\t\t\t<p>{desc}</p>\n\
|
||||
\t\t\t<p>{info}</p>\n";
|
||||
|
||||
pub static WARNING_DOCUMENT: &str = "\
|
||||
\t\t\t<h1>{code}</h1>\n\
|
||||
\t\t\t<h2>{message} :o</h2>\n\
|
||||
\t\t\t<p>{desc}</p>\n\
|
||||
\t\t\t<p>{info}</p>\n";
|
||||
|
||||
pub static SUCCESS_DOCUMENT: &str = "\
|
||||
\t\t\t<h1>{code}</h1>\n\
|
||||
\t\t\t<h2>{message} :)</h2>\n\
|
||||
\t\t\t<p>{desc}</p>\n\
|
||||
\t\t\t<p>{info}</p>\n";
|
||||
|
||||
pub static INFO_DOCUMENT: &str = "\
|
||||
\t\t\t<h1>{code}</h1>\n\
|
||||
\t\t\t<h2>{message} :)</h2>\n\
|
||||
\t\t\t<p>{desc}</p>\n\
|
||||
\t\t\t<p>{info}</p>\n";
|
||||
|
@ -1,13 +1,14 @@
|
||||
use super::Method;
|
||||
use crate::usimp;
|
||||
use crate::websocket;
|
||||
use chrono;
|
||||
use json;
|
||||
|
||||
pub struct HttpStream {
|
||||
stream: super::Stream,
|
||||
request_num: u32,
|
||||
client_keep_alive: bool,
|
||||
server_keep_alive: bool,
|
||||
pub stream: super::Stream,
|
||||
pub request_num: u32,
|
||||
pub client_keep_alive: bool,
|
||||
pub server_keep_alive: bool,
|
||||
}
|
||||
|
||||
pub fn connection_handler(client: super::Stream) {
|
||||
@ -29,20 +30,10 @@ pub fn connection_handler(client: super::Stream) {
|
||||
|
||||
fn request_handler(client: &mut super::HttpStream) {
|
||||
let mut res = super::Response::new();
|
||||
res.add_header("Server", "Locutus");
|
||||
res.add_header(
|
||||
"Date",
|
||||
chrono::Utc::now()
|
||||
.format("%a, %d %b %Y %H:%M:%S GMT")
|
||||
.to_string()
|
||||
.as_str(),
|
||||
);
|
||||
|
||||
let req = super::parser::parse_request(&mut client.stream).unwrap();
|
||||
println!("{} {}", req.method, req.uri);
|
||||
|
||||
let doc = "<!DOCTYPE html><html><head><title>Locutus Server</title></head><body><h1>Hello World! :D</h1></body></html>";
|
||||
|
||||
if !req.uri.starts_with("/")
|
||||
|| req.uri.contains("/./")
|
||||
|| req.uri.contains("/../")
|
||||
@ -53,16 +44,22 @@ fn request_handler(client: &mut super::HttpStream) {
|
||||
res.status(404);
|
||||
} else if req.uri.eq("/") {
|
||||
res.status(200);
|
||||
res.add_header("Content-Length", doc.len().to_string().as_str());
|
||||
res.add_header("Content-Type", "text/html; charset=utf-8");
|
||||
} else if req.uri.eq("/_usimp/websocket") {
|
||||
return websocket::connection_handler(client, &req);
|
||||
} else if req.uri.starts_with("/_usimp/") {
|
||||
let parts: Vec<&str> = req.uri.split('/').collect();
|
||||
match parts[2..] {
|
||||
["entity", entity] => res.status(501),
|
||||
[func] => match usimp::is_valid(func) {
|
||||
[endpoint] => match usimp::is_valid_endpoint(endpoint) {
|
||||
true => match req.method {
|
||||
Method::POST => res.status(200),
|
||||
_ => res.status(405),
|
||||
Method::POST => {
|
||||
// TODO
|
||||
res.status(200)
|
||||
}
|
||||
_ => {
|
||||
res.status(405);
|
||||
res.add_header("Allow", "POST");
|
||||
}
|
||||
},
|
||||
false => res.status(400),
|
||||
},
|
||||
@ -73,6 +70,5 @@ fn request_handler(client: &mut super::HttpStream) {
|
||||
}
|
||||
|
||||
res.send(&mut client.stream).unwrap();
|
||||
client.stream.write_all(doc.as_bytes()).unwrap();
|
||||
client.server_keep_alive = false;
|
||||
}
|
||||
|
@ -95,16 +95,18 @@ impl StatusClass {
|
||||
pub struct Status {
|
||||
code: u16,
|
||||
message: String,
|
||||
desc: &'static str,
|
||||
class: StatusClass,
|
||||
}
|
||||
|
||||
impl Status {
|
||||
pub fn from_code(status_code: u16) -> Status {
|
||||
for (code, class, msg, _desc) in &consts::HTTP_STATUSES {
|
||||
for (code, class, msg, desc) in &consts::HTTP_STATUSES {
|
||||
if *code == status_code {
|
||||
return Status {
|
||||
code: status_code,
|
||||
message: msg.to_string(),
|
||||
desc,
|
||||
class: class.clone(),
|
||||
};
|
||||
}
|
||||
@ -116,10 +118,12 @@ impl Status {
|
||||
if status_code < 100 || status_code > 599 {
|
||||
panic!("invalid status code");
|
||||
}
|
||||
let status = Status::from_code(status_code);
|
||||
Status {
|
||||
code: status_code,
|
||||
message: message.to_string(),
|
||||
class: StatusClass::from_code(status_code),
|
||||
desc: status.desc,
|
||||
class: status.class,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -137,8 +141,8 @@ impl std::fmt::Display for HeaderField {
|
||||
|
||||
pub struct Request {
|
||||
version: String,
|
||||
method: Method,
|
||||
uri: String,
|
||||
pub method: Method,
|
||||
pub uri: String,
|
||||
header_fields: Vec<HeaderField>,
|
||||
}
|
||||
|
||||
@ -149,7 +153,7 @@ pub struct Response {
|
||||
}
|
||||
|
||||
impl Response {
|
||||
fn new() -> Response {
|
||||
pub fn new() -> Response {
|
||||
Response {
|
||||
version: "1.1".to_string(),
|
||||
status: Status::from_code(200),
|
||||
@ -157,18 +161,73 @@ impl Response {
|
||||
}
|
||||
}
|
||||
|
||||
fn status(&mut self, status_code: u16) {
|
||||
pub fn status(&mut self, status_code: u16) {
|
||||
self.status = Status::from_code(status_code)
|
||||
}
|
||||
|
||||
fn add_header(&mut self, name: &str, value: &str) {
|
||||
pub fn add_header(&mut self, name: &str, value: &str) {
|
||||
self.header_fields.push(HeaderField {
|
||||
name: String::from(name),
|
||||
value: String::from(value),
|
||||
});
|
||||
}
|
||||
|
||||
fn send(&self, stream: &mut Stream) -> Result<(), std::io::Error> {
|
||||
pub fn find_header(&self, header_name: &str) -> Option<String> {
|
||||
for field in &self.header_fields {
|
||||
if field
|
||||
.name
|
||||
.to_lowercase()
|
||||
.eq(header_name.to_ascii_lowercase().as_str())
|
||||
{
|
||||
return Some(field.value.clone());
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
pub fn send(&mut self, stream: &mut Stream) -> Result<(), std::io::Error> {
|
||||
self.add_header("Server", "Locutus");
|
||||
self.add_header(
|
||||
"Date",
|
||||
chrono::Utc::now()
|
||||
.format("%a, %d %b %Y %H:%M:%S GMT")
|
||||
.to_string()
|
||||
.as_str(),
|
||||
);
|
||||
|
||||
let mut buf = None;
|
||||
if let None = self.find_header("Content-Length") {
|
||||
let (doc, color_name, color) = match self.status.class {
|
||||
StatusClass::Informational => (consts::INFO_DOCUMENT, "info", "#606060"),
|
||||
StatusClass::Success => (consts::SUCCESS_DOCUMENT, "success", "#008000"),
|
||||
StatusClass::Redirection => (consts::WARNING_DOCUMENT, "warning", "#E0C000"),
|
||||
StatusClass::ClientError => (consts::ERROR_DOCUMENT, "error", "#C00000"),
|
||||
StatusClass::ServerError => (consts::ERROR_DOCUMENT, "error", "#C00000"),
|
||||
};
|
||||
|
||||
let new_buf = consts::DEFAULT_DOCUMENT
|
||||
.replace("{status_code}", self.status.code.to_string().as_str())
|
||||
.replace("{status_message}", self.status.message.as_str())
|
||||
.replace("{hostname}", "localhost") // TODO hostname
|
||||
.replace("{theme_color}", color)
|
||||
.replace("{color_name}", color_name)
|
||||
.replace("{server_str}", "Locutus server") // TODO server string
|
||||
.replace(
|
||||
"{doc}",
|
||||
doc.replace("{code}", self.status.code.to_string().as_str())
|
||||
.replace("{message}", self.status.message.as_str())
|
||||
.replace("{desc}", self.status.desc)
|
||||
.replace("{info}", "") // TODO info string
|
||||
.as_str(),
|
||||
)
|
||||
.replace("{{", "{")
|
||||
.replace("}}", "}");
|
||||
|
||||
self.add_header("Content-Length", new_buf.len().to_string().as_str());
|
||||
self.add_header("Content-Type", "text/html; charset=utf-8");
|
||||
buf = Some(new_buf);
|
||||
}
|
||||
|
||||
let mut header = format!(
|
||||
"HTTP/{} {:03} {}\r\n",
|
||||
self.version, self.status.code, self.status.message
|
||||
@ -177,7 +236,12 @@ impl Response {
|
||||
header.push_str(format!("{}: {}\r\n", header_field.name, header_field.value).as_str());
|
||||
}
|
||||
header.push_str("\r\n");
|
||||
stream.write_all(header.as_bytes())
|
||||
|
||||
stream.write_all(header.as_bytes())?;
|
||||
if let Some(buf) = buf {
|
||||
stream.write_all(buf.as_bytes());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
mod http;
|
||||
mod udp;
|
||||
mod usimp;
|
||||
mod websocket;
|
||||
|
||||
use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod, SslStream};
|
||||
use std::net::{TcpListener, UdpSocket};
|
||||
|
@ -1,3 +1,7 @@
|
||||
pub fn is_valid(evt: &str) -> bool {
|
||||
use json;
|
||||
|
||||
pub fn is_valid_endpoint(endpoint: &str) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn endpoint(endpoint: &str, input: json::object::Object) {}
|
||||
|
17
src/websocket/mod.rs
Normal file
17
src/websocket/mod.rs
Normal file
@ -0,0 +1,17 @@
|
||||
use crate::http;
|
||||
|
||||
pub fn connection_handler(client: &mut http::HttpStream, req: &http::Request) {
|
||||
client.server_keep_alive = false;
|
||||
let mut res = http::Response::new();
|
||||
|
||||
if let http::Method::GET = req.method {
|
||||
res.status(501);
|
||||
} else {
|
||||
res.status(405);
|
||||
res.add_header("Allow", "GET");
|
||||
res.send(&mut client.stream);
|
||||
return;
|
||||
}
|
||||
|
||||
res.send(&mut client.stream);
|
||||
}
|
Reference in New Issue
Block a user