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);
+ }
+};