restructure + parser + readme updates
This commit is contained in:
parent
c726e6cf28
commit
b4265fe9c3
115
.clang-format
Normal file
115
.clang-format
Normal file
|
@ -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
|
||||||
|
...
|
38
.clang-tidy
Normal file
38
.clang-tidy
Normal file
|
@ -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'
|
||||||
|
...
|
||||||
|
|
||||||
|
|
13
Makefile
13
Makefile
|
@ -1,4 +1,4 @@
|
||||||
.PHONY: format
|
.PHONY: format lint build build-release build-docker run debug
|
||||||
|
|
||||||
build:
|
build:
|
||||||
g++ main.cpp -g -o anthracite
|
g++ main.cpp -g -o anthracite
|
||||||
|
@ -10,7 +10,16 @@ build-docker:
|
||||||
docker build . -t anthracite
|
docker build . -t anthracite
|
||||||
|
|
||||||
run: build
|
run: build
|
||||||
./anthracite
|
./anthracite 8080
|
||||||
|
|
||||||
|
debug: build
|
||||||
|
gdb --args ./anthracite 8080
|
||||||
|
|
||||||
format:
|
format:
|
||||||
clang-format *.cpp -i
|
clang-format *.cpp -i
|
||||||
|
|
||||||
|
lint:
|
||||||
|
clang-tidy *.cpp
|
||||||
|
|
||||||
|
lint-fix:
|
||||||
|
clang-tidy *.cpp -fix -fix-errors
|
||||||
|
|
52
README.md
52
README.md
|
@ -1,10 +1,54 @@
|
||||||
# Anthracite
|
# 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
|
## Todo
|
||||||
- [x] Serve HTML Pages
|
- [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
|
- [ ] Fix glaring security issues
|
||||||
- [ ] Better parsing & response generation
|
- [ ] Faster parsing
|
||||||
- [ ] Multithreading
|
- [ ] Multithreading
|
||||||
- [ ] Speed optimizations such as keepint the most visited html pages in memory
|
- [ ] Speed optimizations such as keepint the most visited html pages in memory
|
||||||
- [ ] Cleanup codebase
|
- [ ] 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 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)
|
![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"_
|
||||||
|
|
6
backends/backend.cpp
Normal file
6
backends/backend.cpp
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
#include "../http.cpp"
|
||||||
|
|
||||||
|
class backend {
|
||||||
|
public:
|
||||||
|
virtual http_response handle_request(http_request req) {};
|
||||||
|
};
|
21
backends/file_backend.cpp
Normal file
21
backends/file_backend.cpp
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
};
|
12
error_pages/500.html
Normal file
12
error_pages/500.html
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<html>
|
||||||
|
<head><title>Not Found</title></head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<center>
|
||||||
|
<h1>500 - Internal Server Error</h1>
|
||||||
|
<hr>
|
||||||
|
<p>Anthracite/0.0.1</p>
|
||||||
|
<p><small>Created by Nicholas Orlowsky </small> - <a href="https://github.com/nickorlow/anthracite"><small>This is Open Source Software</small></a></p>
|
||||||
|
</center>
|
||||||
|
</body>
|
||||||
|
</html>
|
420
http.cpp
Normal file
420
http.cpp
Normal file
|
@ -0,0 +1,420 @@
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <malloc.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <exception>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#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<string, http_method> 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<http_method, string> 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<int, string> 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<std::string, http_version> 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<http_version, std::string> 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<string, http_header> _headers; // kinda goofy, whatever
|
||||||
|
unordered_map<string, query_param> _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<string, http_header> _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;
|
||||||
|
}
|
||||||
|
};
|
164
main.cpp
164
main.cpp
|
@ -1,4 +1,6 @@
|
||||||
|
#include "backends/file_backend.cpp"
|
||||||
#include <exception>
|
#include <exception>
|
||||||
|
#include <fmt/core.h>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <netinet/in.h>
|
#include <netinet/in.h>
|
||||||
|
@ -9,157 +11,37 @@
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
class Socket {
|
void log_request_an_response(http_request req, http_response resp);
|
||||||
private:
|
|
||||||
int server_socket;
|
|
||||||
int client_socket;
|
|
||||||
|
|
||||||
public:
|
constexpr int default_port = 80;
|
||||||
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;
|
|
||||||
|
|
||||||
server_socket = socket(AF_INET, SOCK_STREAM, 0);
|
int main(int argc, char** argv)
|
||||||
int x = 1;
|
{
|
||||||
setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &x, sizeof(x));
|
int port_number = default_port;
|
||||||
bind(server_socket, (struct sockaddr *)&address, sizeof(address));
|
|
||||||
|
|
||||||
::listen(server_socket, max_queue);
|
if (argc > 1) {
|
||||||
|
port_number = atoi(argv[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void wait_for_conn() { client_socket = accept(server_socket, NULL, NULL); }
|
cout << "Initializing Anthracite\n";
|
||||||
|
anthracite_socket s(port_number);
|
||||||
void close_conn() {
|
cout << "Initialization Complete\n";
|
||||||
close(client_socket);
|
cout << "Listening for HTTP connections on port " << port_number << "\n";
|
||||||
client_socket = -1;
|
file_backend fb;
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
enum http_method { GET, POST, PUT, PATCH, UNKNOWN };
|
|
||||||
static unordered_map<string, http_method> const http_method_map = {
|
|
||||||
{"GET", http_method::GET}};
|
|
||||||
|
|
||||||
static unordered_map<int, string> 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];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
while (true) {
|
||||||
s.wait_for_conn();
|
s.wait_for_conn();
|
||||||
cout << "Received Request...\n";
|
http_request req(s);
|
||||||
http_request req(s.recv_message(8190));
|
http_response resp = fb.handle_request(req);
|
||||||
|
log_request_an_response(req, resp);
|
||||||
string response = "";
|
s.send_message(resp.to_string());
|
||||||
|
|
||||||
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();
|
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";
|
||||||
|
}
|
||||||
|
|
78
socket.cpp
Normal file
78
socket.cpp
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <exception>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <malloc.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <sstream>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
Loading…
Reference in a new issue