format + README

This commit is contained in:
Nicholas Orlowsky 2025-02-04 11:44:34 -05:00
parent 71be773d49
commit 0ebdb34601
Signed by: nickorlow
GPG key ID: 838827D8C4611687
12 changed files with 370 additions and 345 deletions

View file

@ -5,6 +5,7 @@ project(anthracite)
set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED True) set(CMAKE_CXX_STANDARD_REQUIRED True)
set(CMAKE_CXX_FLAGS_RELEASE "-O3") set(CMAKE_CXX_FLAGS_RELEASE "-O3")
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
add_custom_target(build-version add_custom_target(build-version
COMMAND cd ../build_supp && ./version.sh COMMAND cd ../build_supp && ./version.sh

View file

@ -3,9 +3,9 @@ A simple web server written in C++. Supports HTTP 1.0 & 1.1.
## Developing ## Developing
To build/develop Anthracite, you must have C++20, Make, and Python3 installed. To build/develop Anthracite, you must have C++23, CMake, Make, and Python3 installed.
You can run Anthracite with: `make run` Create a `build/` directory, run `cmake ..`, and then `make` to build.
## Todo ## Todo
- [x] HTTP/1.0 - [x] HTTP/1.0
@ -14,14 +14,14 @@ You can run Anthracite with: `make run`
- [x] Add module-based backend system for handling requests - [x] Add module-based backend system for handling requests
- [x] Multithreading - [x] Multithreading
- [x] HTTP/1.1 - [x] HTTP/1.1
- [ ] Improve benchmarking infrastructure - [x] Enhance logging
- [-] Build out module-based backend system for handling requests
- [ ] Faster parsing - [ ] Faster parsing
- [ ] HTTP/2
- [ ] Improve benchmarking infrastructure
- [ ] Fix glaring security issues - [ ] Fix glaring security issues
- [ ] Proper error handling - [ ] Proper error handling
- [ ] User configuration - [ ] User configuration
- [ ] Build out module-based backend system for handling requests
- [ ] HTTP/2
- [ ] Enhance logging
- [ ] Cleanup (this one will never truly be done) - [ ] Cleanup (this one will never truly be done)
## Screenshots ## Screenshots

2
format.sh Executable file
View file

@ -0,0 +1,2 @@
git ls-files -- '*.cpp' '*.h' | xargs clang-format -i -style=file
git diff --exit-code --color

View file

@ -1,15 +1,15 @@
#include "backends/file_backend.hpp"
#include "./anthracite.hpp" #include "./anthracite.hpp"
#include "./log/log.hpp"
#include "./socket/socket.hpp"
#include "backends/file_backend.hpp"
#include <condition_variable> #include <condition_variable>
#include <iostream> #include <iostream>
#include <mutex> #include <mutex>
#include <netinet/in.h> #include <netinet/in.h>
#include <span>
#include <sys/socket.h> #include <sys/socket.h>
#include <thread> #include <thread>
#include <unistd.h> #include <unistd.h>
#include <span>
#include "./log/log.hpp"
#include "./socket/socket.hpp"
using namespace anthracite; using namespace anthracite;
@ -44,7 +44,7 @@ void handle_client(socket::anthracite_socket s, backends::backend& b, backends::
thread_wait_condvar.notify_one(); thread_wait_condvar.notify_one();
} }
//int main(int argc, char** argv) // int main(int argc, char** argv)
int anthracite_main(int argc, char** argv, backends::backend& be) int anthracite_main(int argc, char** argv, backends::backend& be)
{ {
log::logger.initialize(log::LOG_LEVEL_INFO); log::logger.initialize(log::LOG_LEVEL_INFO);

View file

@ -1,13 +1,12 @@
#include "./file_backend.hpp" #include "./file_backend.hpp"
#include "../log/log.hpp" #include "../log/log.hpp"
#include <fstream>
#include <filesystem> #include <filesystem>
#include <fstream>
namespace anthracite::backends { namespace anthracite::backends {
std::unique_ptr<http::response> file_backend::handle_request_cache(http::request& req)
std::unique_ptr<http::response> file_backend::handle_request_cache(http::request& req) { {
std::string filename = req.path() == "/" ? "/index.html" : req.path(); std::string filename = req.path() == "/" ? "/index.html" : req.path();
filename = file_dir + filename; filename = file_dir + filename;
auto file_info = file_cache.find(filename); auto file_info = file_cache.find(filename);
@ -32,9 +31,10 @@ namespace anthracite::backends {
resp->add_header(http::header("Content-Type", content_type), false); resp->add_header(http::header("Content-Type", content_type), false);
return resp; return resp;
} }
void file_backend::populate_cache_dir(std::string dir) { void file_backend::populate_cache_dir(std::string dir)
{
std::filesystem::recursive_directory_iterator cur = begin(std::filesystem::recursive_directory_iterator(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)); std::filesystem::recursive_directory_iterator fin = end(std::filesystem::recursive_directory_iterator(dir));
@ -48,23 +48,27 @@ namespace anthracite::backends {
log::verbose << "File at " << filename << " cached (" << file_cache[filename].size() << " bytes)" << std::endl; log::verbose << "File at " << filename << " cached (" << file_cache[filename].size() << " bytes)" << std::endl;
++cur; ++cur;
} }
} }
void file_backend::populate_cache() { void file_backend::populate_cache()
{
populate_cache_dir(file_dir); populate_cache_dir(file_dir);
populate_cache_dir("./error_pages/"); populate_cache_dir("./error_pages/");
} }
file_backend::file_backend(std::string dir) : file_dir(std::move(dir)) { file_backend::file_backend(std::string dir)
: file_dir(std::move(dir))
{
populate_cache(); populate_cache();
} }
std::unique_ptr<http::response> file_backend::handle_request(http::request& req)
std::unique_ptr<http::response> file_backend::handle_request(http::request& req) { {
return handle_request_cache(req); return handle_request_cache(req);
} }
std::unique_ptr<http::response> file_backend::handle_error(const http::status_codes& error) { std::unique_ptr<http::response> file_backend::handle_error(const http::status_codes& error)
{
std::string filename = "./error_pages/" + std::to_string(error) + ".html"; std::string filename = "./error_pages/" + std::to_string(error) + ".html";
auto file_info = file_cache.find(filename); auto file_info = file_cache.find(filename);
@ -82,6 +86,6 @@ namespace anthracite::backends {
resp->add_header(http::header("Content-Type", "text/html"), false); resp->add_header(http::header("Content-Type", "text/html"), false);
return resp; return resp;
} }
}; };

View file

@ -1,13 +1,14 @@
#include "request.hpp" #include "request.hpp"
#include "constants.hpp"
#include "../log/log.hpp" #include "../log/log.hpp"
#include "constants.hpp"
#include <stdio.h> #include <stdio.h>
namespace anthracite::http { namespace anthracite::http {
request::request(std::string& raw_data, const std::string& client_ip) request::request(std::string& raw_data, const std::string& client_ip)
: _path(""), _client_ipaddr(client_ip) : _path("")
{ , _client_ipaddr(client_ip)
{
parser_state state = METHOD; parser_state state = METHOD;
@ -113,36 +114,39 @@ namespace anthracite::http {
} break; } break;
} }
} }
} }
std::string request::path() { return _path; } std::string request::path() { return _path; }
method request::get_method() { return _method; } method request::get_method() { return _method; }
std::string request::client_ip() { return _client_ipaddr; } std::string request::client_ip() { return _client_ipaddr; }
version request::get_http_version() { version request::get_http_version()
{
return _http_version; return _http_version;
} }
bool request::is_supported_version() { bool request::is_supported_version()
//log::err << reverse_version_map.find(_http_version)->second << std::endl; {
// log::err << reverse_version_map.find(_http_version)->second << std::endl;
return _http_version == HTTP_1_1 || _http_version == HTTP_1_0; return _http_version == HTTP_1_1 || _http_version == HTTP_1_0;
} }
bool request::close_connection() { bool request::close_connection()
{
const auto& header = _headers.find("Connection"); const auto& header = _headers.find("Connection");
const bool found = header != _headers.end(); const bool found = header != _headers.end();
if(found && header->second.value() == "keep-alive") { if (found && header->second.value() == "keep-alive") {
return false; return false;
} }
return true; return true;
} }
std::string request::to_string() std::string request::to_string()
{ {
std::string response = ""; std::string response = "";
response += reverse_method_map.find(_method)->second + " " + _path + "?"; response += reverse_method_map.find(_method)->second + " " + _path + "?";
@ -160,6 +164,6 @@ namespace anthracite::http {
response += _body_content; response += _body_content;
return response; return response;
} }
}; };

View file

@ -3,37 +3,40 @@
namespace anthracite::http { namespace anthracite::http {
response::response() {}; response::response() {};
int response::status_code() { return _status_code; } int response::status_code() { return _status_code; }
void response::add_body(const std::string body) { void response::add_body(const std::string body)
{
_content_noref = body; _content_noref = body;
_content = &_content_noref; _content = &_content_noref;
} }
void response::add_body_ref(std::string* body) { void response::add_body_ref(std::string* body)
{
_content = body; _content = body;
} }
void response::add_header(header header, bool override_existing) void response::add_header(header header, bool override_existing)
{ {
if (override_existing || _headers.find(header.name()) == _headers.end()) { if (override_existing || _headers.find(header.name()) == _headers.end()) {
_headers[header.name()] = header; _headers[header.name()] = header;
} }
} }
void response::add_status(int code) { void response::add_status(int code)
{
_status_code = code; _status_code = code;
} }
std::string& response::content() std::string& response::content()
{ {
return *_content; return *_content;
} }
std::string response::header_to_string() std::string response::header_to_string()
{ {
std::string response = ""; std::string response = "";
response += "HTTP/1.1 " + std::to_string(_status_code) + " " + status_map.find(_status_code)->second + "\r\n"; response += "HTTP/1.1 " + std::to_string(_status_code) + " " + status_map.find(_status_code)->second + "\r\n";
@ -48,11 +51,11 @@ namespace anthracite::http {
response += "\r\n"; response += "\r\n";
return response; return response;
} }
std::string response::to_string() std::string response::to_string()
{ {
return header_to_string() + *_content; return header_to_string() + *_content;
} }
}; };

View file

@ -1,22 +1,29 @@
#include "./log.hpp" #include "./log.hpp"
namespace anthracite::log { namespace anthracite::log {
enum LOG_LEVEL Logger::_level = LOG_LEVEL_NONE; enum LOG_LEVEL Logger::_level = LOG_LEVEL_NONE;
// TODO: implement logger as a singleton to prevent duplicates // TODO: implement logger as a singleton to prevent duplicates
Logger::Logger() = default; Logger::Logger() = default;
void Logger::initialize(enum LOG_LEVEL level) { void Logger::initialize(enum LOG_LEVEL level)
{
_level = level; _level = level;
} }
LogBuf::LogBuf(std::ostream& output_stream, const std::string& tag, enum LOG_LEVEL level) : _output_stream(output_stream), _tag(tag), _level(level) {} LogBuf::LogBuf(std::ostream& output_stream, const std::string& tag, enum LOG_LEVEL level)
: _output_stream(output_stream)
, _tag(tag)
, _level(level)
{
}
int LogBuf::sync() { int LogBuf::sync()
{
if (this->_level <= logger._level) { if (this->_level <= logger._level) {
std::cout << "[" << this ->_tag << "] " << this->str(); std::cout << "[" << this->_tag << "] " << this->str();
std::cout.flush(); std::cout.flush();
} }
this->str(""); this->str("");
return 0; return 0;
} }
}; };

View file

@ -1,3 +1,4 @@
#include "./socket.hpp"
#include <arpa/inet.h> #include <arpa/inet.h>
#include <array> #include <array>
#include <malloc.h> #include <malloc.h>
@ -7,15 +8,13 @@
#include <sys/time.h> #include <sys/time.h>
#include <unistd.h> #include <unistd.h>
#include <vector> #include <vector>
#include "./socket.hpp"
namespace anthracite::socket { namespace anthracite::socket {
anthracite_socket::anthracite_socket(int port, int max_queue) anthracite_socket::anthracite_socket(int port, int max_queue)
: server_socket(::socket(AF_INET, SOCK_STREAM, 0)) : server_socket(::socket(AF_INET, SOCK_STREAM, 0))
, client_ip("") , client_ip("")
{ {
struct sockaddr_in address {}; struct sockaddr_in address {};
address.sin_family = AF_INET; address.sin_family = AF_INET;
address.sin_port = htons(port); address.sin_port = htons(port);
@ -26,38 +25,38 @@ anthracite_socket::anthracite_socket(int port, int max_queue)
bind(server_socket, reinterpret_cast<struct sockaddr*>(&address), sizeof(address)); bind(server_socket, reinterpret_cast<struct sockaddr*>(&address), sizeof(address));
listen(server_socket, max_queue); listen(server_socket, max_queue);
} }
void anthracite_socket::wait_for_conn() void anthracite_socket::wait_for_conn()
{ {
client_ip = ""; client_ip = "";
client_socket = accept(server_socket, reinterpret_cast<struct sockaddr*>(&client_addr), &client_addr_len); client_socket = accept(server_socket, reinterpret_cast<struct sockaddr*>(&client_addr), &client_addr_len);
std::array<char, INET_ADDRSTRLEN> ip_str { 0 }; std::array<char, INET_ADDRSTRLEN> ip_str { 0 };
inet_ntop(AF_INET, &client_addr.sin_addr, ip_str.data(), INET_ADDRSTRLEN); inet_ntop(AF_INET, &client_addr.sin_addr, ip_str.data(), INET_ADDRSTRLEN);
client_ip = std::string(ip_str.data()); client_ip = std::string(ip_str.data());
} }
const std::string& anthracite_socket::get_client_ip() const std::string& anthracite_socket::get_client_ip()
{ {
return client_ip; return client_ip;
} }
void anthracite_socket::close_conn() void anthracite_socket::close_conn()
{ {
close(client_socket); close(client_socket);
client_socket = -1; client_socket = -1;
} }
void anthracite_socket::send_message(std::string& msg) void anthracite_socket::send_message(std::string& msg)
{ {
if (client_socket == -1) { if (client_socket == -1) {
return; return;
} }
send(client_socket, &msg[0], msg.length(), 0); send(client_socket, &msg[0], msg.length(), 0);
} }
std::string anthracite_socket::recv_message(int buffer_size) std::string anthracite_socket::recv_message(int buffer_size)
{ {
if (client_socket == -1) { if (client_socket == -1) {
return ""; return "";
} }
@ -72,6 +71,6 @@ anthracite_socket::anthracite_socket(int port, int max_queue)
response[buffer_size] = '\0'; response[buffer_size] = '\0';
return { response.data() }; return { response.data() };
} }
}; };

View file

@ -17,9 +17,7 @@ private:
std::string client_ip; std::string client_ip;
struct sockaddr_in client_addr {}; struct sockaddr_in client_addr {};
socklen_t client_addr_len {}; socklen_t client_addr_len {};
static constexpr struct timeval timeout_tv { static const struct timeval timeout_tv;
.tv_sec = 5, .tv_usec = 0
};
public: public:
anthracite_socket(int port, int max_queue = MAX_QUEUE_LENGTH); anthracite_socket(int port, int max_queue = MAX_QUEUE_LENGTH);

View file

@ -1,30 +1,33 @@
#include "../lib/anthracite.hpp" #include "../lib/anthracite.hpp"
#include "../lib/backends/backend.hpp" #include "../lib/backends/backend.hpp"
#include "../lib/http/constants.hpp" #include "../lib/http/constants.hpp"
#include <iostream>
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <sstream> #include <sstream>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
#include <iostream>
using namespace anthracite; using namespace anthracite;
using CallbackType = std::unique_ptr<http::response> (*)(http::request&); using CallbackType = std::unique_ptr<http::response> (*)(http::request&);
class api_backend : public backends::backend { class api_backend : public backends::backend {
class RouteNode { class RouteNode {
public: public:
std::optional<CallbackType> callback; std::optional<CallbackType> callback;
RouteNode() : callback(std::nullopt) {} RouteNode()
: callback(std::nullopt)
{
}
std::unordered_map<std::string, RouteNode> routes; std::unordered_map<std::string, RouteNode> routes;
}; };
RouteNode root; RouteNode root;
std::unique_ptr<http::response> default_route(http::request& req) { std::unique_ptr<http::response> default_route(http::request& req)
{
std::unique_ptr<http::response> resp = std::make_unique<http::response>(); std::unique_ptr<http::response> resp = std::make_unique<http::response>();
resp->add_body("Not Found"); resp->add_body("Not Found");
@ -34,14 +37,15 @@ class api_backend : public backends::backend {
return resp; return resp;
} }
std::unique_ptr<http::response> find_handler(http::request& req) { std::unique_ptr<http::response> find_handler(http::request& req)
{
std::string filename = req.path().substr(1); std::string filename = req.path().substr(1);
std::vector<std::string> result; std::vector<std::string> result;
std::stringstream ss (filename); std::stringstream ss(filename);
std::string item; std::string item;
RouteNode* cur = &root; RouteNode* cur = &root;
while (getline (ss, item, '/')) { while (getline(ss, item, '/')) {
if (cur->routes.find(item) == cur->routes.end()) { if (cur->routes.find(item) == cur->routes.end()) {
if (cur->routes.find("*") == cur->routes.end()) { if (cur->routes.find("*") == cur->routes.end()) {
break; break;
@ -58,28 +62,28 @@ class api_backend : public backends::backend {
} else { } else {
return default_route(req); return default_route(req);
} }
} }
std::unique_ptr<http::response> handle_request(http::request& req) override { std::unique_ptr<http::response> handle_request(http::request& req) override
{
return find_handler(req); return find_handler(req);
} }
public: public:
api_backend()
api_backend() { {
root.routes = std::unordered_map<std::string, RouteNode>(); root.routes = std::unordered_map<std::string, RouteNode>();
} }
void register_endpoint(std::string pathspec, CallbackType callback) { void register_endpoint(std::string pathspec, CallbackType callback)
{
std::vector<std::string> result; std::vector<std::string> result;
std::stringstream ss (pathspec); std::stringstream ss(pathspec);
std::string item; std::string item;
RouteNode* cur = &root; RouteNode* cur = &root;
while (getline (ss, item, '/')) { while (getline(ss, item, '/')) {
cur->routes[item] = RouteNode{}; cur->routes[item] = RouteNode {};
cur = &cur->routes[item]; cur = &cur->routes[item];
} }
@ -87,7 +91,8 @@ class api_backend : public backends::backend {
} }
}; };
std::unique_ptr<http::response> handle_request(http::request& req) { std::unique_ptr<http::response> handle_request(http::request& req)
{
std::unique_ptr<http::response> resp = std::make_unique<http::response>(); std::unique_ptr<http::response> resp = std::make_unique<http::response>();
resp->add_body(R"({"user": "endpoint"}")"); resp->add_body(R"({"user": "endpoint"}")");
@ -97,7 +102,8 @@ std::unique_ptr<http::response> handle_request(http::request& req) {
return resp; return resp;
} }
int main(int argc, char** argv) { int main(int argc, char** argv)
{
auto args = std::span(argv, size_t(argc)); auto args = std::span(argv, size_t(argc));
api_backend ab; api_backend ab;
ab.register_endpoint("users/*", handle_request); ab.register_endpoint("users/*", handle_request);

View file

@ -3,7 +3,8 @@
using namespace anthracite; using namespace anthracite;
int main(int argc, char** argv) { int main(int argc, char** argv)
{
auto args = std::span(argv, size_t(argc)); auto args = std::span(argv, size_t(argc));
backends::file_backend fb(argc > 2 ? args[2] : "./www"); backends::file_backend fb(argc > 2 ? args[2] : "./www");
anthracite_main(argc, argv, fb); anthracite_main(argc, argv, fb);