diff --git a/src/http/consts.rs b/src/http/consts.rs
index d81469d..5728c1f 100644
--- a/src/http/consts.rs
+++ b/src/http/consts.rs
@@ -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} :&#xFEFF;(</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} :&#xFEFF;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} :&#xFEFF;)</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} :&#xFEFF;)</h2>\n\
+    \t\t\t<p>{desc}</p>\n\
+    \t\t\t<p>{info}</p>\n";
diff --git a/src/http/handler.rs b/src/http/handler.rs
index a203a26..3c33647 100644
--- a/src/http/handler.rs
+++ b/src/http/handler.rs
@@ -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;
 }
diff --git a/src/http/mod.rs b/src/http/mod.rs
index 04ced60..6fdc7fb 100644
--- a/src/http/mod.rs
+++ b/src/http/mod.rs
@@ -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(())
     }
 }
 
diff --git a/src/main.rs b/src/main.rs
index a50ce5f..89a8470 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,6 +1,7 @@
 mod http;
 mod udp;
 mod usimp;
+mod websocket;
 
 use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod, SslStream};
 use std::net::{TcpListener, UdpSocket};
diff --git a/src/udp.rs b/src/udp/mod.rs
similarity index 100%
rename from src/udp.rs
rename to src/udp/mod.rs
diff --git a/src/usimp/mod.rs b/src/usimp/mod.rs
index 4e16b6f..72dc0e5 100644
--- a/src/usimp/mod.rs
+++ b/src/usimp/mod.rs
@@ -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) {}
diff --git a/src/websocket/mod.rs b/src/websocket/mod.rs
new file mode 100644
index 0000000..7a56c89
--- /dev/null
+++ b/src/websocket/mod.rs
@@ -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);
+}