From 6c9f7f7c4983f0d5103f3b71a2c3ea58cbaf81e0 Mon Sep 17 00:00:00 2001 From: Nicholas Orlowsky Date: Wed, 18 Oct 2023 14:09:26 -0400 Subject: [PATCH] restructuring --- src/backends/backend.cpp | 6 +- src/backends/file_backend.cpp | 31 ++- src/datastructures/smart_map.cpp | 80 ++++++ src/error_pages/404.html | 2 +- src/error_pages/500.html | 2 +- src/http.cpp | 456 ------------------------------- src/http/constants.cpp | 173 ++++++++++++ src/http/header_query.cpp | 55 ++++ src/http/http.hpp | 7 + src/http/http_request.cpp | 159 +++++++++++ src/http/http_response.cpp | 60 ++++ src/main.cpp | 24 +- src/socket.cpp | 14 +- 13 files changed, 573 insertions(+), 496 deletions(-) create mode 100644 src/datastructures/smart_map.cpp delete mode 100644 src/http.cpp create mode 100644 src/http/constants.cpp create mode 100644 src/http/header_query.cpp create mode 100644 src/http/http.hpp create mode 100644 src/http/http_request.cpp create mode 100644 src/http/http_response.cpp diff --git a/src/backends/backend.cpp b/src/backends/backend.cpp index e15576d..bc262bf 100644 --- a/src/backends/backend.cpp +++ b/src/backends/backend.cpp @@ -1,7 +1,9 @@ -#include "../http.cpp" #include +#include "../http/http_request.cpp" +#include "../http/http_response.cpp" + class backend { public: - virtual unique_ptr handle_request(http_request& req) = 0; + virtual std::unique_ptr handle_request(http_request& req) = 0; }; diff --git a/src/backends/file_backend.cpp b/src/backends/file_backend.cpp index 80f51b5..3e5631f 100644 --- a/src/backends/file_backend.cpp +++ b/src/backends/file_backend.cpp @@ -1,13 +1,13 @@ -#include "backend.cpp" #include +#include "backend.cpp" class file_backend : public backend { private: - unordered_map file_cache; - string file_dir; + std::unordered_map file_cache; + std::string file_dir; - unique_ptr handle_request_cache(http_request& req) { - string filename = req.path() == "/" ? "/index.html" : req.path(); + std::unique_ptr handle_request_cache(http_request& req) { + std::string filename = req.path() == "/" ? "/index.html" : req.path(); filename = file_dir + filename; auto file_info = file_cache.find(filename); @@ -18,21 +18,21 @@ private: file_info = file_cache.find(filename); } - return make_unique(file_info->second, filename, status); + return std::make_unique(file_info->second, filename, status); } - void populate_cache_dir(string dir) { - filesystem::recursive_directory_iterator cur = begin(filesystem::recursive_directory_iterator(dir)); - filesystem::recursive_directory_iterator fin = end(filesystem::recursive_directory_iterator(dir)); + void populate_cache_dir(std::string dir) { + std::filesystem::recursive_directory_iterator cur = begin(std::filesystem::recursive_directory_iterator(dir)); + std::filesystem::recursive_directory_iterator fin = end(std::filesystem::recursive_directory_iterator(dir)); while (cur != fin) { auto p = cur->path(); - string filename = p.string(); - stringstream buffer; - ifstream stream(filename); + std::string filename = p.string(); + std::stringstream buffer; + std::ifstream stream(filename); buffer << stream.rdbuf(); file_cache[filename] = buffer.str(); - cout << "File at " << filename << " cached (" << file_cache[filename].size() << " bytes)" << endl; + std::cout << "File at " << filename << " cached (" << file_cache[filename].size() << " bytes)" << std::endl; ++cur; } } @@ -40,14 +40,15 @@ private: void populate_cache() { populate_cache_dir(file_dir); populate_cache_dir("./error_pages/"); + } public: - file_backend(string dir = "./www") : file_dir(dir) { + file_backend(std::string dir = "./www") : file_dir(dir) { populate_cache(); } - unique_ptr handle_request(http_request& req) override { + std::unique_ptr handle_request(http_request& req) override { return handle_request_cache(req); } }; diff --git a/src/datastructures/smart_map.cpp b/src/datastructures/smart_map.cpp new file mode 100644 index 0000000..0e19134 --- /dev/null +++ b/src/datastructures/smart_map.cpp @@ -0,0 +1,80 @@ +#include +#include +#include +#include +#include +#include +#include + +constexpr int benchmark_loops = 1000000; + +template +class smart_map { + private: + bool use_hmap = false; + std::unordered_map hmap; + + double assess_hmap(const std::vector> check) { + const auto& start = std::chrono::high_resolution_clock::now(); + for(int i = 0; i < benchmark_loops; i++) { + for(const auto& check_item : check) { + assert(check_item.second == hmap[check_item.first]); + } + } + const auto& end = std::chrono::high_resolution_clock::now(); + auto duration = std::chrono::duration(end - start).count(); + return duration; + } + + double assess_vmap(const std::vector> check) { + const auto& start = std::chrono::high_resolution_clock::now(); + for(int i = 0; i < benchmark_loops; i++) { + for(const auto& check_item : check) { + for(const auto& item : hmap) { + if(check_item.first == item.first) { + assert(check_item.second == item.second); + } + } + } + } + const auto& end = std::chrono::high_resolution_clock::now(); + auto duration = std::chrono::duration(end - start).count(); + return duration; + } + + public: + smart_map () = default; + + void assess_datastructure() { + std::vector> vals(hmap.begin(), hmap.end()); + std::shuffle(vals.begin(), vals.end(), std::default_random_engine(570)); + use_hmap = assess_hmap(vals) > assess_vmap(vals); + } + + bool will_use_hmap() { + return use_hmap; + } + + ValueType* get(const KeyType& key) { + if(use_hmap) { + if(hmap.find(key) != hmap.end()) { + return &hmap[key]; + } else { + return NULL; + } + } + + for(auto& item : hmap) { + if(item.first == key) { + std::string& ref = item.second; + return &ref; + } + } + + return NULL; + } + + void insert(const KeyType key, const ValueType value) { + hmap[key] = value; + } +}; diff --git a/src/error_pages/404.html b/src/error_pages/404.html index 50cdb27..ce0c9eb 100644 --- a/src/error_pages/404.html +++ b/src/error_pages/404.html @@ -6,7 +6,7 @@

404 - Not Found


Anthracite/0.0.1

-

Created by Nicholas Orlowsky - This is Open Source Software

+

This is Open Source Software

diff --git a/src/error_pages/500.html b/src/error_pages/500.html index 19ee712..86247fc 100644 --- a/src/error_pages/500.html +++ b/src/error_pages/500.html @@ -6,7 +6,7 @@

500 - Internal Server Error


Anthracite/0.0.1

-

Created by Nicholas Orlowsky - This is Open Source Software

+

This is Open Source Software

diff --git a/src/http.cpp b/src/http.cpp deleted file mode 100644 index dac322b..0000000 --- a/src/http.cpp +++ /dev/null @@ -1,456 +0,0 @@ -#include -#include -#include -#include -#include - -#include "socket.cpp" -#include -#include -#include -#include -#include -#include -#include - -using namespace std; - -constexpr int HTTP_HEADER_BYTES = 8190; - -enum http_method { - GET, - POST, - DELETE, - PUT, - PATCH, - HEAD, - OPTIONS, - CONNECT, - TRACE, - COPY, - LINK, - UNLINK, - PURGE, - LOCK, - UNLOCK, - PROPFIND, - VIEW, - UNKNOWN -}; - -static unordered_map const http_method_map = { - { "GET", http_method::GET }, - { "POST", http_method::POST }, - { "DELETE", http_method::DELETE }, - { "PUT", http_method::PUT }, - { "PATCH", http_method::PATCH }, - { "HEAD", http_method::HEAD }, - { "OPTIONS", http_method::OPTIONS }, - { "CONNECT", http_method::CONNECT }, - { "TRACE", http_method::TRACE }, - { "COPY", http_method::COPY }, - { "LINK", http_method::LINK }, - { "UNLINK", http_method::UNLINK }, - { "PURGE", http_method::PURGE }, - { "LOCK", http_method::LOCK }, - { "UNLOCK", http_method::UNLOCK }, - { "PROPFIND", http_method::PROPFIND }, - { "VIEW", http_method::VIEW }, - { "UNKNOWN", http_method::UNKNOWN } -}; - -static unordered_map const http_reverse_method_map = { - { http_method::GET, "GET" }, - { http_method::POST, "POST" }, - { http_method::DELETE, "DELETE" }, - { http_method::PUT, "PUT" }, - { http_method::PATCH, "PATCH" }, - { http_method::HEAD, "HEAD" }, - { http_method::OPTIONS, "OPTIONS" }, - { http_method::CONNECT, "CONNECT" }, - { http_method::TRACE, "TRACE" }, - { http_method::COPY, "COPY" }, - { http_method::LINK, "LINK" }, - { http_method::UNLINK, "UNLINK" }, - { http_method::PURGE, "PURGE" }, - { http_method::LOCK, "LOCK" }, - { http_method::UNLOCK, "UNLOCK" }, - { http_method::PROPFIND, "PROPFIND" }, - { http_method::VIEW, "VIEW" }, - { http_method::UNKNOWN, "UNKNOWN" } -}; - -static unordered_map const http_status_map = { - { 100, "CONTINUE" }, - { 101, "SWITCHING PROTOCOLS" }, - { 200, "OK" }, - { 201, "CREATED" }, - { 202, "ACCEPTED" }, - { 203, "NON-AUTHORITATIVE INFORMATION" }, - { 204, "NO CONTENT" }, - { 205, "RESET CONTENT" }, - { 206, "PARTIAL CONTENT" }, - { 300, "MULTIPLE CHOICES" }, - { 301, "MOVED PERMANENTLY" }, - { 302, "FOUND" }, - { 303, "SEE OTHER" }, - { 304, "NOT MODIFIED" }, - { 305, "USE PROXY" }, - { 307, "TEMPORARY REDIRECT" }, - { 400, "BAD REQUEST" }, - { 401, "UNAUTHORIZED" }, - { 402, "PAYMENT REQUIRED" }, - { 403, "FORBIDDEN" }, - { 404, "NOT FOUND" }, - { 405, "METHOD NOT ALLOWED" }, - { 406, "NOT ACCEPTABLE" }, - { 407, "PROXY AUTHENTICATION REQUIRED" }, - { 408, "REQUEST TIMEOUT" }, - { 409, "CONFLICT" }, - { 410, "GONE" }, - { 411, "LENGTH REQUIRED" }, - { 412, "PRECONDITION FAILED" }, - { 413, "PAYLOAD TOO LARGE" }, - { 414, "URI TOO LONG" }, - { 415, "UNSUPPORTED MEDIA TYPE" }, - { 416, "RANGE NOT SATISFIABLE" }, - { 417, "EXPECTATION FAILED" }, - { 418, "I'M A TEAPOT" }, - { 421, "MISDIRECTED REQUEST" }, - { 422, "UNPROCESSABLE ENTITY" }, - { 423, "LOCKED" }, - { 424, "FAILED DEPENDENCY" }, - { 426, "UPGRADE REQUIRED" }, - { 428, "PRECONDITION REQUIRED" }, - { 429, "TOO MANY REQUESTS" }, - { 431, "REQUEST HEADER FIELDS TOO LARGE" }, - { 451, "UNAVAILABLE FOR LEGAL REASONS" }, - { 500, "INTERNAL SERVER ERROR" }, - { 501, "NOT IMPLEMENTED" }, - { 502, "BAD GATEWAY" }, - { 503, "SERVICE UNAVAILABLE" }, - { 504, "GATEWAY TIMEOUT" }, - { 505, "HTTP VERSION NOT SUPPORTED" }, - { 506, "VARIANT ALSO NEGOTIATES" }, - { 507, "INSUFFICIENT STORAGE" }, - { 508, "LOOP DETECTED" }, - { 510, "NOT EXTENDED" }, - { 511, "NETWORK AUTHENTICATION REQUIRED" }, - { 420, "ENHANCE YOUR CALM" } -}; - -static unordered_map const mime_types = { - { "html", "text/html" }, - { "css", "text/css" }, - - { "js", "application/javascript" }, - { "pdf", "application/pdf" }, - - { "ico", "image/x-icon" }, - { "jpg", "image/jpeg" }, - { "jpeg", "image/jpeg" }, - { "png", "image/png" }, - { "gif", "image/gif" }, - { "bmp", "image/bmp" }, - - { "mp4", "video/mp4" }, - { "avi", "video/x-msvideo" }, - { "mkv", "video/x-matroska" }, - { "mov", "video/quicktime" }, - { "wmv", "video/x-ms-wmv" }, -}; - -class name_value { -private: - string _name; - string _value; - -protected: - name_value() {} - -public: - name_value(string name, string value) - : _name(std::move(name)) - , _value(std::move(value)) - { - } - virtual ~name_value() = default; - name_value(const name_value&) = default; - name_value& operator=(name_value const&) = default; - name_value(name_value&&) = default; - name_value& operator=(name_value&&) = default; - - string name() { return _name; } - string value() { return _value; } - - virtual string to_string() { return ""; } -}; - -class http_header : public name_value { -public: - http_header() - : name_value() - { - } - http_header(string name, string value) - : name_value(name, value) - { - } - - string to_string() override { return name() + ": " + value() + "\r\n"; } -}; - -class query_param : public name_value { -public: - query_param() - : name_value() - { - } - query_param(string name, string value) - : name_value(name, value) - { - } - - string to_string() override { return name() + "=" + value(); } -}; - -enum http_version { HTTP_0_9, - HTTP_1_0, - HTTP_1_1, - HTTP_2_0, - HTTP_3_0 }; - -static std::unordered_map const http_version_map = { - { "HTTP/0.9", HTTP_0_9 }, - { "HTTP/1.0", HTTP_1_0 }, - { "HTTP/1.1", HTTP_1_1 }, - { "HTTP/2.0", HTTP_2_0 }, - { "HTTP/3.0", HTTP_3_0 } -}; - -static std::unordered_map const http_reverse_version_map = { - { HTTP_0_9, "HTTP/0.9" }, - { HTTP_1_0, "HTTP/1.0" }, - { HTTP_1_1, "HTTP/1.1" }, - { HTTP_2_0, "HTTP/2.0" }, - { HTTP_3_0, "HTTP/3.0" } -}; - -class http_request { -private: - enum parser_state { METHOD, - PATH, - QUERY_PARAM_NAME, - QUERY_PARAM_VALUE, - VERSION, - HEADER_NAME, - HEADER_VALUE, - BODY_CONTENT }; - http_method _method; - http_version _http_version; - string _path; - string _client_ipaddr; - string _body_content; - unordered_map _headers; // kinda goofy, whatever - unordered_map _query_params; // kinda goofy, whatever - -public: - http_request(anthracite_socket& s) - : _path("") - { - string raw_data = s.recv_message(HTTP_HEADER_BYTES); - _client_ipaddr = s.get_client_ip(); - - parser_state state = METHOD; - - string scratch = ""; - string scratch_2 = ""; - for (int i = 0; i < raw_data.length(); i++) { - switch (state) { - case METHOD: { - if (raw_data[i] == ' ') { - if (http_method_map.find(scratch) == http_method_map.end()) { - _method = http_method::UNKNOWN; - } else { - _method = http_method_map.find(scratch)->second; - } - scratch = ""; - state = PATH; - } else { - scratch += raw_data[i]; - } - } break; - - case PATH: { - switch (raw_data[i]) { - case ' ': - state = VERSION; - break; - case '?': - state = QUERY_PARAM_NAME; - break; - default: - _path += raw_data[i]; - break; - } - } break; - - case QUERY_PARAM_NAME: { - if (raw_data[i] == ' ') { - scratch = ""; - state = VERSION; - } else if (raw_data[i] == '=') { - state = QUERY_PARAM_VALUE; - } else { - scratch += raw_data[i]; - } - } break; - - case QUERY_PARAM_VALUE: { - if (raw_data[i] == ' ') { - _query_params[scratch] = query_param(scratch, scratch_2); - scratch = ""; - scratch_2 = ""; - state = VERSION; - } else if (raw_data[i] == '&') { - _query_params[scratch] = query_param(scratch, scratch_2); - scratch = ""; - scratch_2 = ""; - state = QUERY_PARAM_NAME; - } else { - scratch_2 += raw_data[i]; - } - } break; - - case VERSION: { - if (raw_data[i] == '\n') { - _http_version = http_version_map.find(scratch)->second; - scratch = ""; - state = HEADER_NAME; - } else if (raw_data[i] != '\r') { - scratch += raw_data[i]; - } - } break; - - case HEADER_NAME: { - if (raw_data[i] == '\n') { - scratch = ""; - scratch_2 = ""; - state = BODY_CONTENT; - break; - } else if (raw_data[i] == ' ') { - scratch = ""; - cout << "Error: Whitespace found in header name\n"; - break; - } else if (raw_data[i] == ':') { - state = HEADER_VALUE; - i++; - } else { - scratch += raw_data[i]; - } - } break; - - case HEADER_VALUE: { - if (raw_data[i] == '\n') { - _headers[scratch] = http_header(scratch, scratch_2); - scratch = ""; - scratch_2 = ""; - state = HEADER_NAME; - } else if (raw_data[i] != '\r') { - scratch_2 += raw_data[i]; - } - } break; - - case BODY_CONTENT: { - _body_content += raw_data[i]; - } break; - } - } - } - - string path() { return _path; } - - http_method method() { return _method; } - - string client_ip() { return _client_ipaddr; } - - string to_string() - { - string response = ""; - response += http_reverse_method_map.find(_method)->second + " " + _path + "?"; - - for (auto qp : _query_params) { - response += qp.second.to_string() + "&"; - } - - response += " " + http_reverse_version_map.find(_http_version)->second + "\r\n"; - - for (auto header : _headers) { - response += header.second.to_string(); - } - - response += "\r\n"; - response += _body_content; - - return response; - } -}; - -class http_response { -private: - int _status_code; - string& _content; - string _filename; - unordered_map _headers; // kinda goofy, whatever - -public: - http_response(string& content, string filename, int status_code = 200) - : _content(content) - , _status_code(status_code) - , _filename(std::move(filename)) - { - } - - int status_code() { return _status_code; } - - void add_header(http_header header, bool override_existing = true) - { - if (override_existing || _headers.find(header.name()) == _headers.end()) { - _headers[header.name()] = header; - } - } - - string& content() - { - return _content; - } - - string header_to_string() - { - string response = ""; - response += "HTTP/1.1 " + ::to_string(_status_code) + " " + http_status_map.find(_status_code)->second + "\r\n"; - string content_type = "text/html"; - string file_extension = _filename.substr(_filename.rfind('.') + 1); - auto mime_type = mime_types.find(file_extension); - if (mime_type != mime_types.end()) { - content_type = mime_type->second; - } - add_header(http_header("Content-Type", content_type), false); - add_header(http_header("Content-Length", ::to_string(_content.length())), false); - add_header(http_header("Server", "Anthracite/0.0.1"), false); - add_header(http_header("Origin-Server", "Anthracite/0.0.1"), false); - - for (auto header : _headers) { - response += header.second.to_string(); - } - - response += "\r\n"; - - return response; - } - - string to_string() - { - return header_to_string() + _content; - } -}; diff --git a/src/http/constants.cpp b/src/http/constants.cpp new file mode 100644 index 0000000..d1a32a7 --- /dev/null +++ b/src/http/constants.cpp @@ -0,0 +1,173 @@ +#include +#include + +constexpr int HTTP_HEADER_BYTES = 8190; + +enum http_method { + GET, + POST, + DELETE, + PUT, + PATCH, + HEAD, + OPTIONS, + CONNECT, + TRACE, + COPY, + LINK, + UNLINK, + PURGE, + LOCK, + UNLOCK, + PROPFIND, + VIEW, + UNKNOWN +}; + +static std::unordered_map const http_method_map = { + { "GET", http_method::GET }, + { "POST", http_method::POST }, + { "DELETE", http_method::DELETE }, + { "PUT", http_method::PUT }, + { "PATCH", http_method::PATCH }, + { "HEAD", http_method::HEAD }, + { "OPTIONS", http_method::OPTIONS }, + { "CONNECT", http_method::CONNECT }, + { "TRACE", http_method::TRACE }, + { "COPY", http_method::COPY }, + { "LINK", http_method::LINK }, + { "UNLINK", http_method::UNLINK }, + { "PURGE", http_method::PURGE }, + { "LOCK", http_method::LOCK }, + { "UNLOCK", http_method::UNLOCK }, + { "PROPFIND", http_method::PROPFIND }, + { "VIEW", http_method::VIEW }, + { "UNKNOWN", http_method::UNKNOWN } +}; + +static std::unordered_map const http_reverse_method_map = { + { http_method::GET, "GET" }, + { http_method::POST, "POST" }, + { http_method::DELETE, "DELETE" }, + { http_method::PUT, "PUT" }, + { http_method::PATCH, "PATCH" }, + { http_method::HEAD, "HEAD" }, + { http_method::OPTIONS, "OPTIONS" }, + { http_method::CONNECT, "CONNECT" }, + { http_method::TRACE, "TRACE" }, + { http_method::COPY, "COPY" }, + { http_method::LINK, "LINK" }, + { http_method::UNLINK, "UNLINK" }, + { http_method::PURGE, "PURGE" }, + { http_method::LOCK, "LOCK" }, + { http_method::UNLOCK, "UNLOCK" }, + { http_method::PROPFIND, "PROPFIND" }, + { http_method::VIEW, "VIEW" }, + { http_method::UNKNOWN, "UNKNOWN" } +}; + +static std::unordered_map const http_status_map = { + { 100, "CONTINUE" }, + { 101, "SWITCHING PROTOCOLS" }, + { 200, "OK" }, + { 201, "CREATED" }, + { 202, "ACCEPTED" }, + { 203, "NON-AUTHORITATIVE INFORMATION" }, + { 204, "NO CONTENT" }, + { 205, "RESET CONTENT" }, + { 206, "PARTIAL CONTENT" }, + { 300, "MULTIPLE CHOICES" }, + { 301, "MOVED PERMANENTLY" }, + { 302, "FOUND" }, + { 303, "SEE OTHER" }, + { 304, "NOT MODIFIED" }, + { 305, "USE PROXY" }, + { 307, "TEMPORARY REDIRECT" }, + { 400, "BAD REQUEST" }, + { 401, "UNAUTHORIZED" }, + { 402, "PAYMENT REQUIRED" }, + { 403, "FORBIDDEN" }, + { 404, "NOT FOUND" }, + { 405, "METHOD NOT ALLOWED" }, + { 406, "NOT ACCEPTABLE" }, + { 407, "PROXY AUTHENTICATION REQUIRED" }, + { 408, "REQUEST TIMEOUT" }, + { 409, "CONFLICT" }, + { 410, "GONE" }, + { 411, "LENGTH REQUIRED" }, + { 412, "PRECONDITION FAILED" }, + { 413, "PAYLOAD TOO LARGE" }, + { 414, "URI TOO LONG" }, + { 415, "UNSUPPORTED MEDIA TYPE" }, + { 416, "RANGE NOT SATISFIABLE" }, + { 417, "EXPECTATION FAILED" }, + { 418, "I'M A TEAPOT" }, + { 421, "MISDIRECTED REQUEST" }, + { 422, "UNPROCESSABLE ENTITY" }, + { 423, "LOCKED" }, + { 424, "FAILED DEPENDENCY" }, + { 426, "UPGRADE REQUIRED" }, + { 428, "PRECONDITION REQUIRED" }, + { 429, "TOO MANY REQUESTS" }, + { 431, "REQUEST HEADER FIELDS TOO LARGE" }, + { 451, "UNAVAILABLE FOR LEGAL REASONS" }, + { 500, "INTERNAL SERVER ERROR" }, + { 501, "NOT IMPLEMENTED" }, + { 502, "BAD GATEWAY" }, + { 503, "SERVICE UNAVAILABLE" }, + { 504, "GATEWAY TIMEOUT" }, + { 505, "HTTP VERSION NOT SUPPORTED" }, + { 506, "VARIANT ALSO NEGOTIATES" }, + { 507, "INSUFFICIENT STORAGE" }, + { 508, "LOOP DETECTED" }, + { 510, "NOT EXTENDED" }, + { 511, "NETWORK AUTHENTICATION REQUIRED" }, + { 420, "ENHANCE YOUR CALM" } +}; + +static std::unordered_map const mime_types = { + { "html", "text/html" }, + { "css", "text/css" }, + + { "js", "application/javascript" }, + { "pdf", "application/pdf" }, + + { "ico", "image/x-icon" }, + { "jpg", "image/jpeg" }, + { "jpeg", "image/jpeg" }, + { "png", "image/png" }, + { "gif", "image/gif" }, + { "bmp", "image/bmp" }, + + { "mp4", "video/mp4" }, + { "avi", "video/x-msvideo" }, + { "mkv", "video/x-matroska" }, + { "mov", "video/quicktime" }, + { "wmv", "video/x-ms-wmv" }, +}; + +enum http_version { HTTP_0_9, + HTTP_1_0, + HTTP_1_1, + HTTP_2_0, + HTTP_3_0 }; + +static std::unordered_map const http_version_map = { + { "HTTP/0.9", HTTP_0_9 }, + { "HTTP/1.0", HTTP_1_0 }, + { "HTTP/1.1", HTTP_1_1 }, + { "HTTP/2.0", HTTP_2_0 }, + { "HTTP/3.0", HTTP_3_0 } +}; + +static std::unordered_map const http_reverse_version_map = { + { HTTP_0_9, "HTTP/0.9" }, + { HTTP_1_0, "HTTP/1.0" }, + { HTTP_1_1, "HTTP/1.1" }, + { HTTP_2_0, "HTTP/2.0" }, + { HTTP_3_0, "HTTP/3.0" } +}; + + + + diff --git a/src/http/header_query.cpp b/src/http/header_query.cpp new file mode 100644 index 0000000..aa530e1 --- /dev/null +++ b/src/http/header_query.cpp @@ -0,0 +1,55 @@ +#include + +class name_value { +private: + std::string _name; + std::string _value; + +protected: + name_value() {} + +public: + name_value(std::string name, std::string value) + : _name(std::move(name)) + , _value(std::move(value)) + { + } + virtual ~name_value() = default; + name_value(const name_value&) = default; + name_value& operator=(name_value const&) = default; + name_value(name_value&&) = default; + name_value& operator=(name_value&&) = default; + + std::string name() { return _name; } + std::string value() { return _value; } + + virtual std::string to_string() { return ""; } +}; + +class http_header : public name_value { +public: + http_header() + : name_value() + { + } + http_header(std::string name, std::string value) + : name_value(name, value) + { + } + + std::string to_string() override { return name() + ": " + value() + "\r\n"; } +}; + +class query_param : public name_value { +public: + query_param() + : name_value() + { + } + query_param(std::string name, std::string value) + : name_value(name, value) + { + } + + std::string to_string() override { return name() + "=" + value(); } +}; diff --git a/src/http/http.hpp b/src/http/http.hpp new file mode 100644 index 0000000..9ae4652 --- /dev/null +++ b/src/http/http.hpp @@ -0,0 +1,7 @@ +#pragma once +#include +#include + +#include "constants.cpp" +#include "header_query.cpp" +#include "../socket.cpp" diff --git a/src/http/http_request.cpp b/src/http/http_request.cpp new file mode 100644 index 0000000..ce4ff67 --- /dev/null +++ b/src/http/http_request.cpp @@ -0,0 +1,159 @@ +#include "http.hpp" + +class http_request { +private: + enum parser_state { METHOD, + PATH, + QUERY_PARAM_NAME, + QUERY_PARAM_VALUE, + VERSION, + HEADER_NAME, + HEADER_VALUE, + BODY_CONTENT }; + http_method _method; + http_version _http_version; + std::string _path; + std::string _client_ipaddr; + std::string _body_content; + std::unordered_map _headers; // kinda goofy, whatever + std::unordered_map _query_params; // kinda goofy, whatever + +public: + http_request(anthracite_socket& s) + : _path(""), _client_ipaddr(s.get_client_ip()) + { + std::string raw_data = s.recv_message(HTTP_HEADER_BYTES); + + parser_state state = METHOD; + + std::string scratch = ""; + std::string scratch_2 = ""; + for (int i = 0; i < raw_data.length(); i++) { + switch (state) { + case METHOD: { + if (raw_data[i] == ' ') { + if (http_method_map.find(scratch) == http_method_map.end()) { + _method = http_method::UNKNOWN; + } else { + _method = http_method_map.find(scratch)->second; + } + scratch = ""; + state = PATH; + } else { + scratch += raw_data[i]; + } + } break; + + case PATH: { + switch (raw_data[i]) { + case ' ': + state = VERSION; + break; + case '?': + state = QUERY_PARAM_NAME; + break; + default: + _path += raw_data[i]; + break; + } + } break; + + case QUERY_PARAM_NAME: { + if (raw_data[i] == ' ') { + scratch = ""; + state = VERSION; + } else if (raw_data[i] == '=') { + state = QUERY_PARAM_VALUE; + } else { + scratch += raw_data[i]; + } + } break; + + case QUERY_PARAM_VALUE: { + if (raw_data[i] == ' ') { + _query_params[scratch] = query_param(scratch, scratch_2); + scratch = ""; + scratch_2 = ""; + state = VERSION; + } else if (raw_data[i] == '&') { + _query_params[scratch] = query_param(scratch, scratch_2); + scratch = ""; + scratch_2 = ""; + state = QUERY_PARAM_NAME; + } else { + scratch_2 += raw_data[i]; + } + } break; + + case VERSION: { + if (raw_data[i] == '\n') { + _http_version = http_version_map.find(scratch)->second; + scratch = ""; + state = HEADER_NAME; + } else if (raw_data[i] != '\r') { + scratch += raw_data[i]; + } + } break; + + case HEADER_NAME: { + if (raw_data[i] == '\n') { + scratch = ""; + scratch_2 = ""; + state = BODY_CONTENT; + break; + } else if (raw_data[i] == ' ') { + scratch = ""; + break; + } else if (raw_data[i] == ':') { + state = HEADER_VALUE; + i++; + } else { + scratch += raw_data[i]; + } + } break; + + case HEADER_VALUE: { + if (raw_data[i] == '\n') { + _headers[scratch] = http_header(scratch, scratch_2); + scratch = ""; + scratch_2 = ""; + state = HEADER_NAME; + } else if (raw_data[i] != '\r') { + scratch_2 += raw_data[i]; + } + } break; + + case BODY_CONTENT: { + _body_content += raw_data[i]; + } break; + } + } + } + + std::string path() { return _path; } + + http_method method() { return _method; } + + std::string client_ip() { return _client_ipaddr; } + + std::string to_string() + { + std::string response = ""; + response += http_reverse_method_map.find(_method)->second + " " + _path + "?"; + + for (auto qp : _query_params) { + response += qp.second.to_string() + "&"; + } + + response += " " + http_reverse_version_map.find(_http_version)->second + "\r\n"; + + for (auto header : _headers) { + response += header.second.to_string(); + } + + response += "\r\n"; + response += _body_content; + + return response; + } +}; diff --git a/src/http/http_response.cpp b/src/http/http_response.cpp new file mode 100644 index 0000000..8f90cfa --- /dev/null +++ b/src/http/http_response.cpp @@ -0,0 +1,60 @@ +#include "http.hpp" + +class http_response { +private: + int _status_code; + std::string& _content; + std::string _filename; + std::unordered_map _headers; // kinda goofy, whatever + +public: + http_response(std::string& content, std::string filename, int status_code = 200) + : _content(content) + , _status_code(status_code) + , _filename(std::move(filename)) + { + } + + int status_code() { return _status_code; } + + void add_header(http_header header, bool override_existing = true) + { + if (override_existing || _headers.find(header.name()) == _headers.end()) { + _headers[header.name()] = header; + } + } + + std::string& content() + { + return _content; + } + + std::string header_to_string() + { + std::string response = ""; + response += "HTTP/1.1 " + std::to_string(_status_code) + " " + http_status_map.find(_status_code)->second + "\r\n"; + std::string content_type = "text/html"; + std::string file_extension = _filename.substr(_filename.rfind('.') + 1); + auto mime_type = mime_types.find(file_extension); + if (mime_type != mime_types.end()) { + content_type = mime_type->second; + } + add_header(http_header("Content-Type", content_type), false); + add_header(http_header("Content-Length", std::to_string(_content.length())), false); + add_header(http_header("Server", "Anthracite/0.0.1"), false); + add_header(http_header("Origin-Server", "Anthracite/0.0.1"), false); + + for (auto header : _headers) { + response += header.second.to_string(); + } + + response += "\r\n"; + + return response; + } + + std::string to_string() + { + return header_to_string() + _content; + } +}; diff --git a/src/main.cpp b/src/main.cpp index 89a0400..890477c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,23 +11,21 @@ #include #include -using namespace std; - -void log_request_and_response(http_request& req, unique_ptr& resp); +void log_request_and_response(http_request& req, std::unique_ptr& resp); constexpr int default_port = 80; constexpr int max_worker_threads = 128; int active_threads = 0; -mutex mtx; -condition_variable cv; +std::mutex mtx; +std::condition_variable cv; void handle_client(anthracite_socket s, file_backend& fb) { http_request req(s); - unique_ptr resp = fb.handle_request(req); + std::unique_ptr resp = fb.handle_request(req); log_request_and_response(req, resp); - string header = resp->header_to_string(); + std::string header = resp->header_to_string(); s.send_message(header); s.send_message(resp->content()); resp.reset(); @@ -47,24 +45,24 @@ int main(int argc, char** argv) port_number = atoi(argv[1]); } - cout << "Initializing Anthracite" << endl; + std::cout << "Initializing Anthracite" << std::endl; anthracite_socket s(port_number); file_backend fb(argc > 2 ? argv[2] : "./www"); - cout << "Initialization Complete" << endl; - cout << "Listening for HTTP connections on port " << port_number << endl; + std::cout << "Initialization Complete" << std::endl; + std::cout << "Listening for HTTP connections on port " << port_number << std::endl; while (true) { s.wait_for_conn(); std::unique_lock lock(mtx); cv.wait(lock, [] { return active_threads < max_worker_threads; }); active_threads++; - thread(handle_client, s, ref(fb)).detach(); + std::thread(handle_client, s, std::ref(fb)).detach(); } exit(0); } -void log_request_and_response(http_request& req, unique_ptr& resp) +void log_request_and_response(http_request& req, std::unique_ptr& resp) { - cout << "[" << resp->status_code() << " " + http_status_map.find(resp->status_code())->second + "] " + req.client_ip() + " " + http_reverse_method_map.find(req.method())->second + " " + req.path() << endl; + std::cout << "[" << resp->status_code() << " " + http_status_map.find(resp->status_code())->second + "] " + req.client_ip() + " " + http_reverse_method_map.find(req.method())->second + " " + req.path() << std::endl; } diff --git a/src/socket.cpp b/src/socket.cpp index 81a5f36..7c48da0 100644 --- a/src/socket.cpp +++ b/src/socket.cpp @@ -9,13 +9,11 @@ #include #include -using namespace std; - class anthracite_socket { private: int server_socket; int client_socket {}; - string client_ip; + std::string client_ip; struct sockaddr_in client_addr {}; socklen_t client_addr_len {}; @@ -42,10 +40,10 @@ public: client_socket = accept(server_socket, (struct sockaddr*)&client_addr, &client_addr_len); char ip_str[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &client_addr.sin_addr, ip_str, INET_ADDRSTRLEN); - client_ip = string(ip_str); + client_ip = std::string(ip_str); } - string get_client_ip() + std::string get_client_ip() { return client_ip; } @@ -56,7 +54,7 @@ public: client_socket = -1; } - void send_message(string& msg) + void send_message(std::string& msg) { if (client_socket == -1) { return; @@ -64,7 +62,7 @@ public: send(client_socket, &msg[0], msg.length(), 0); } - string recv_message(int buffer_size = 1024) + std::string recv_message(int buffer_size = 1024) { if (client_socket == -1) { return ""; @@ -73,6 +71,6 @@ public: char response[buffer_size + 1]; recv(client_socket, response, sizeof(response), 0); response[buffer_size] = '\0'; - return string(response); + return std::string(response); } };