From b4265fe9c3d7f32d8c29d682bc6f71cf76959806 Mon Sep 17 00:00:00 2001 From: Nicholas Orlowsky Date: Thu, 7 Sep 2023 19:33:22 -0400 Subject: [PATCH] restructure + parser + readme updates --- .clang-format | 115 +++++++++++ .clang-tidy | 38 ++++ Makefile | 13 +- README.md | 52 ++++- backends/backend.cpp | 6 + backends/file_backend.cpp | 21 ++ error_pages/500.html | 12 ++ http.cpp | 420 ++++++++++++++++++++++++++++++++++++++ main.cpp | 172 +++------------- socket.cpp | 78 +++++++ 10 files changed, 778 insertions(+), 149 deletions(-) create mode 100644 .clang-format create mode 100644 .clang-tidy create mode 100644 backends/backend.cpp create mode 100644 backends/file_backend.cpp create mode 100644 error_pages/500.html create mode 100644 http.cpp create mode 100644 socket.cpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..0e7c0d5 --- /dev/null +++ b/.clang-format @@ -0,0 +1,115 @@ +--- +# BasedOnStyle: WebKit +AccessModifierOffset: -4 +AlignAfterOpenBracket: DontAlign +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Right +AlignOperands: false +AlignTrailingComments: false +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: No +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: All +BreakBeforeBraces: WebKit +BreakBeforeInheritanceComma: false +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeComma +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 0 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: false +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: false +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeCategories: + - Regex: '^"config\.h"' + Priority: -1 + # The main header for a source file automatically gets category 0 + - Regex: '^<.*SoftLink.h>' + Priority: 4 + - Regex: '^".*SoftLink.h"' + Priority: 3 + - Regex: '^<.*>' + Priority: 2 + - Regex: '.*' + Priority: 1 +IncludeIsMainRegex: '(Test)?$' +IndentCaseLabels: false +IndentWidth: 4 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBlockIndentWidth: 4 +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Left +ReflowComments: true +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +TabWidth: 8 +UseTab: Never +--- +Language: ObjC +PointerAlignment: Right +... diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..a924985 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,38 @@ +--- +Checks: 'clang-diagnostic-*,clang-analyzer-*,cppcoreguidelines-*,modernize-*,-modernize-use-trailing-return-type' +WarningsAsErrors: true +HeaderFilterRegex: '' +AnalyzeTemporaryDtors: false +FormatStyle: google +CheckOptions: + - key: cert-dcl16-c.NewSuffixes + value: 'L;LL;LU;LLU' + - key: cert-oop54-cpp.WarnOnlyIfThisHasSuspiciousField + value: '0' + - key: cppcoreguidelines-explicit-virtual-functions.IgnoreDestructors + value: '1' + - key: cppcoreguidelines-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic + value: '1' + - key: google-readability-braces-around-statements.ShortStatementLines + value: '1' + - key: google-readability-function-size.StatementThreshold + value: '800' + - key: google-readability-namespace-comments.ShortNamespaceLines + value: '10' + - key: google-readability-namespace-comments.SpacesBeforeComments + value: '2' + - key: modernize-loop-convert.MaxCopySize + value: '16' + - key: modernize-loop-convert.MinConfidence + value: reasonable + - key: modernize-loop-convert.NamingStyle + value: CamelCase + - key: modernize-pass-by-value.IncludeStyle + value: llvm + - key: modernize-replace-auto-ptr.IncludeStyle + value: llvm + - key: modernize-use-nullptr.NullMacros + value: 'NULL' +... + + diff --git a/Makefile b/Makefile index 97a758d..bf13ddc 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: format +.PHONY: format lint build build-release build-docker run debug build: g++ main.cpp -g -o anthracite @@ -10,7 +10,16 @@ build-docker: docker build . -t anthracite run: build - ./anthracite + ./anthracite 8080 + +debug: build + gdb --args ./anthracite 8080 format: clang-format *.cpp -i + +lint: + clang-tidy *.cpp + +lint-fix: + clang-tidy *.cpp -fix -fix-errors diff --git a/README.md b/README.md index 0eaf285..2c49c92 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,54 @@ # Anthracite -A web server written in C++ +A simple web server written in C++ + +## Module-Based Backends +Anthracite includes a system for allowing different "backend modules" to handle requests. +This allows for anthracite to be extended for additional use-cases. For example, the following +backends could be implemented: + +- File: Return files from a directory +- Reverse Proxy: Pass the request to another server +- Web Framework: Pass the request into an application built on your favorite web framework + +## Building & Debugging + +Once you have the repository cloned, you can run the following command to build Anthracite: + +```shell +make build +``` + +It will create a binary file named `./anthracite` + +To save time, you can use the following command to build and run anthracite on port `8080`: + +```shell +make run +``` + +To save time again, you can use the following command to build and debug anthracite in gdb: + +```shell +make debug +``` + +## Usage + +Run the following commands to serve all files located in `./www/`: + +```shell +./anthracite [PORT_NUMBER] +``` ## Todo - [x] Serve HTML Pages +- [x] Properly parse HTTP requests +- [x] Add module-based backend system for handling requests +- [ ] Cleanup (this one will never truly be done) +- [ ] Proper error handling +- [ ] Flesh out module-based backend system for handling requests - [ ] Fix glaring security issues -- [ ] Better parsing & response generation +- [ ] Faster parsing - [ ] Multithreading - [ ] Speed optimizations such as keepint the most visited html pages in memory - [ ] Cleanup codebase @@ -15,3 +59,7 @@ A web server written in C++ ![A picture of the default index.html page used by Anthracite](https://github.com/nickorlow/anthracite/blob/main/.screenshots/default-page.png?raw=true) ![A picture of the Anthracite default 404 not found page](https://github.com/nickorlow/anthracite/blob/main/.screenshots/404-page.png?raw=true) + +--- + +_"By industry, we thrive"_ diff --git a/backends/backend.cpp b/backends/backend.cpp new file mode 100644 index 0000000..d417e28 --- /dev/null +++ b/backends/backend.cpp @@ -0,0 +1,6 @@ +#include "../http.cpp" + +class backend { +public: + virtual http_response handle_request(http_request req) {}; +}; diff --git a/backends/file_backend.cpp b/backends/file_backend.cpp new file mode 100644 index 0000000..8a88ab1 --- /dev/null +++ b/backends/file_backend.cpp @@ -0,0 +1,21 @@ +#include "backend.cpp" + +class file_backend : public backend { +public: + http_response handle_request(http_request req) { + string filename = req.path() == "/" ? "index.html" : req.path(); + filename = "./www/" + filename; + ifstream stream(filename); + + int status = 200; + if (!stream.is_open()) { + status = 404; + filename = "./error_pages/404.html"; + stream = ifstream(filename); + } + + stringstream buffer; + buffer << stream.rdbuf(); + return http_response(buffer.str(), status); + } +}; diff --git a/error_pages/500.html b/error_pages/500.html new file mode 100644 index 0000000..19ee712 --- /dev/null +++ b/error_pages/500.html @@ -0,0 +1,12 @@ + + Not Found + + +
+

500 - Internal Server Error

+
+

Anthracite/0.0.1

+

Created by Nicholas Orlowsky - This is Open Source Software

+
+ + diff --git a/http.cpp b/http.cpp new file mode 100644 index 0000000..31dc6be --- /dev/null +++ b/http.cpp @@ -0,0 +1,420 @@ +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "socket.cpp" + +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" } +}; + +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; + unordered_map _headers; // kinda goofy, whatever + +public: + http_response(string content, int status_code = 200) + : _content(std::move(content)) + , _status_code(status_code) + { + } + + 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 to_string() + { + string response = ""; + response += "HTTP/1.1 " + ::to_string(_status_code) + " " + http_status_map.find(_status_code)->second + "\r\n"; + + add_header(http_header("Content-Type", "text/html"), false); + add_header(http_header("Content-Length", ::to_string(_content.length())), false); + add_header(http_header("Server", "Anthracite/0.0.1"), false); + + for (auto header : _headers) { + response += header.second.to_string(); + } + + response += "\r\n"; + response += _content; + + return response; + } +}; diff --git a/main.cpp b/main.cpp index 51ecf7e..378b886 100644 --- a/main.cpp +++ b/main.cpp @@ -1,4 +1,6 @@ +#include "backends/file_backend.cpp" #include +#include #include #include #include @@ -9,157 +11,37 @@ using namespace std; -class Socket { -private: - int server_socket; - int client_socket; +void log_request_an_response(http_request req, http_response resp); -public: - Socket(int port, int max_queue = 10) { - struct sockaddr_in address; - address.sin_family = AF_INET; - address.sin_port = htons(port); - address.sin_addr.s_addr = INADDR_ANY; +constexpr int default_port = 80; - server_socket = socket(AF_INET, SOCK_STREAM, 0); - int x = 1; - setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &x, sizeof(x)); - bind(server_socket, (struct sockaddr *)&address, sizeof(address)); +int main(int argc, char** argv) +{ + int port_number = default_port; - ::listen(server_socket, max_queue); - } - - void wait_for_conn() { client_socket = accept(server_socket, NULL, NULL); } - - void close_conn() { - close(client_socket); - client_socket = -1; - } - - void send_message(string msg) { - if (client_socket == -1) { - return; - } - send(client_socket, &msg[0], msg.length(), 0); - } - - string recv_message(int buffer_size = 1024) { - if (client_socket == -1) { - return ""; + if (argc > 1) { + port_number = atoi(argv[1]); } - char response[buffer_size + 1]; - recv(client_socket, response, sizeof(response), 0); - response[buffer_size] = '\0'; - return string(response); - } -}; + cout << "Initializing Anthracite\n"; + anthracite_socket s(port_number); + cout << "Initialization Complete\n"; + cout << "Listening for HTTP connections on port " << port_number << "\n"; + file_backend fb; -enum http_method { GET, POST, PUT, PATCH, UNKNOWN }; -static unordered_map const http_method_map = { - {"GET", http_method::GET}}; - -static unordered_map const http_status_map = { - {200, "OK"}, - {404, "NOT FOUND"}, -}; - -class http_request { -private: - http_method _method; - string _path; - -public: - http_request(string raw_data) { - int state = 0; - string method = ""; - for (int i = 0; i < raw_data.length() && state != 2; i++) { - - if (raw_data[i] == ' ') { - state++; - continue; - } - - if (state == 0) { - method += raw_data[i]; - } else if (state == 1) { - _path += raw_data[i]; - } + while (true) { + s.wait_for_conn(); + http_request req(s); + http_response resp = fb.handle_request(req); + log_request_an_response(req, resp); + s.send_message(resp.to_string()); + s.close_conn(); } - if (http_method_map.find(method) == http_method_map.end()) { - _method = http_method::UNKNOWN; - } else { - _method = http_method_map.find(method)->second; - } - } - - string path() { return _path; } - - http_method method() { return _method; } -}; - -class http_response { -private: - int _status_code; - string _content; - -public: - http_response(string content, int status_code = 200) { - _content = content; - _status_code = status_code; - } - - string to_string() { - string response = ""; - response += "HTTP/1.1 " + ::to_string(_status_code) + " " + - http_status_map.find(_status_code)->second + " \n"; - response += "Content-Type: text/html\n"; - response += "Content-Length: " + ::to_string(_content.length()) + "\n"; - response += "Server: Anthracite/0.0.1\n\n"; - response += _content; - return response; - } -}; - -int main() { - cout << "Initializing Server...\n"; - Socket s(8099); - cout << "Initialized, Awaiting Connections...\n"; - - while (true) { - s.wait_for_conn(); - cout << "Received Request...\n"; - http_request req(s.recv_message(8190)); - - string response = ""; - - switch (req.method()) { - case http_method::GET: { - string filename = req.path() == "/" ? "index.html" : req.path(); - filename = "./www/" + filename; - ifstream stream(filename); - - int status = 200; - if (!stream.is_open()) { - status = 404; - filename = "./error_pages/404.html"; - stream = ifstream(filename); - } - - stringstream buffer; - buffer << stream.rdbuf(); - http_response res(buffer.str(), status); - response = res.to_string(); - } break; - - default: - break; - } - cout << "Responding with: \n" << response << "\n"; - s.send_message(response); - s.close_conn(); - } - - return 0; + return 0; +} + +void log_request_an_response(http_request req, http_response 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() + "\n"; } diff --git a/socket.cpp b/socket.cpp new file mode 100644 index 0000000..ae840be --- /dev/null +++ b/socket.cpp @@ -0,0 +1,78 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +class anthracite_socket { +private: + int server_socket; + int client_socket {}; + string client_ip; + struct sockaddr_in client_addr {}; + socklen_t client_addr_len {}; + +public: + anthracite_socket(int port, int max_queue = 10) + : server_socket(socket(AF_INET, SOCK_STREAM, 0)) + , client_ip("") + { + struct sockaddr_in address {}; + address.sin_family = AF_INET; + address.sin_port = htons(port); + address.sin_addr.s_addr = INADDR_ANY; + + int x = 1; + setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &x, sizeof(x)); + bind(server_socket, (struct sockaddr*)&address, sizeof(address)); + + ::listen(server_socket, max_queue); + } + + void wait_for_conn() + { + client_ip = ""; + 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); + } + + string get_client_ip() + { + return client_ip; + } + + void close_conn() + { + close(client_socket); + client_socket = -1; + } + + void send_message(string msg) + { + if (client_socket == -1) { + return; + } + send(client_socket, &msg[0], msg.length(), 0); + } + + string recv_message(int buffer_size = 1024) + { + if (client_socket == -1) { + return ""; + } + + char response[buffer_size + 1]; + recv(client_socket, response, sizeof(response), 0); + response[buffer_size] = '\0'; + return string(response); + } +};