split into src and lib and socket changes

* libanthracite files are now all in lib/
* anthracite-bin + anthracite-api-bin files are now all in src/
* socket split into header and source properly
This commit is contained in:
Nicholas Orlowsky 2025-02-04 11:37:46 -05:00
parent fba87f3fbb
commit 71be773d49
Signed by: nickorlow
GPG key ID: 838827D8C4611687
24 changed files with 49 additions and 121 deletions

81
lib/anthracite.cpp Normal file
View file

@ -0,0 +1,81 @@
#include "backends/file_backend.hpp"
#include "./anthracite.hpp"
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <netinet/in.h>
#include <sys/socket.h>
#include <thread>
#include <unistd.h>
#include <span>
#include "./log/log.hpp"
#include "./socket/socket.hpp"
using namespace anthracite;
void log_request_and_response(http::request& req, std::unique_ptr<http::response>& resp);
constexpr int default_port = 80;
constexpr int max_worker_threads = 128;
void handle_client(socket::anthracite_socket s, backends::backend& b, backends::file_backend& fb, std::mutex& thread_wait_mutex, std::condition_variable& thread_wait_condvar, int& active_threads)
{
while (true) {
std::string raw_request = s.recv_message(http::HEADER_BYTES);
if (raw_request == "") {
break;
}
http::request req(raw_request, s.get_client_ip());
std::unique_ptr<http::response> resp = req.is_supported_version() ? b.handle_request(req) : fb.handle_error(http::status_codes::HTTP_VERSION_NOT_SUPPORTED);
log_request_and_response(req, resp);
std::string header = resp->header_to_string();
s.send_message(header);
s.send_message(resp->content());
resp.reset();
if (req.close_connection()) {
break;
}
}
s.close_conn();
{
std::lock_guard<std::mutex> lock(thread_wait_mutex);
active_threads--;
}
thread_wait_condvar.notify_one();
}
//int main(int argc, char** argv)
int anthracite_main(int argc, char** argv, backends::backend& be)
{
log::logger.initialize(log::LOG_LEVEL_INFO);
auto args = std::span(argv, size_t(argc));
int port_number = default_port;
if (argc > 1) {
port_number = atoi(args[1]);
}
log::verbose << "Initializing Anthracite" << std::endl;
socket::anthracite_socket s(port_number);
backends::file_backend fb(argc > 2 ? args[2] : "./www");
log::verbose << "Initialization Complete" << std::endl;
log::info << "Listening for HTTP connections on port " << port_number << std::endl;
int active_threads = 0;
std::mutex thread_wait_mutex;
std::condition_variable thread_wait_condvar;
while (true) {
s.wait_for_conn();
std::unique_lock<std::mutex> lock(thread_wait_mutex);
thread_wait_condvar.wait(lock, [active_threads] { return active_threads < max_worker_threads; });
active_threads++;
std::thread(handle_client, s, std::ref(be), std::ref(fb), std::ref(thread_wait_mutex), std::ref(thread_wait_condvar), std::ref(active_threads)).detach();
}
exit(0);
}
void log_request_and_response(http::request& req, std::unique_ptr<http::response>& resp)
{
log::info << "[" << resp->status_code() << " " + http::status_map.find(resp->status_code())->second + "] " + req.client_ip() + " " + http::reverse_method_map.find(req.get_method())->second + " " + req.path() << std::endl;
}

4
lib/anthracite.hpp Normal file
View file

@ -0,0 +1,4 @@
#include "backends/backend.hpp"
using namespace anthracite;
int anthracite_main(int argc, char** argv, backends::backend& be);

20
lib/backends/backend.hpp Normal file
View file

@ -0,0 +1,20 @@
#pragma once
#include <memory>
#include "../http/request.hpp"
#include "../http/response.hpp"
namespace anthracite::backends {
class backend {
public:
backend() = default;
virtual ~backend() = default;
backend(backend const&) = delete;
backend& operator = (backend const&) = delete;
backend(backend&&) = delete;
backend& operator=(backend&&) = delete;
virtual std::unique_ptr<http::response> handle_request(http::request& req) = 0;
};
};

View file

@ -0,0 +1,87 @@
#include "./file_backend.hpp"
#include "../log/log.hpp"
#include <fstream>
#include <filesystem>
namespace anthracite::backends {
std::unique_ptr<http::response> file_backend::handle_request_cache(http::request& req) {
std::string filename = req.path() == "/" ? "/index.html" : req.path();
filename = file_dir + filename;
auto file_info = file_cache.find(filename);
int status = http::status_codes::OK;
if (file_info == file_cache.end()) {
return handle_error(http::status_codes::NOT_FOUND);
}
std::unique_ptr<http::response> resp = std::make_unique<http::response>();
std::string file_extension = file_info->first.substr(file_info->first.rfind('.') + 1);
std::string content_type = "text/html";
auto mime_type = http::mime_types.find(file_extension);
if (mime_type != http::mime_types.end()) {
content_type = mime_type->second;
}
resp->add_body_ref(&file_info->second);
resp->add_status(http::status_codes::OK);
resp->add_header(http::header("Content-Type", content_type), false);
return resp;
}
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 fin = end(std::filesystem::recursive_directory_iterator(dir));
while (cur != fin) {
auto p = cur->path();
std::string filename = p.string();
std::stringstream buffer;
std::ifstream stream(filename);
buffer << stream.rdbuf();
file_cache[filename] = buffer.str();
log::verbose << "File at " << filename << " cached (" << file_cache[filename].size() << " bytes)" << std::endl;
++cur;
}
}
void file_backend::populate_cache() {
populate_cache_dir(file_dir);
populate_cache_dir("./error_pages/");
}
file_backend::file_backend(std::string dir) : file_dir(std::move(dir)) {
populate_cache();
}
std::unique_ptr<http::response> file_backend::handle_request(http::request& req) {
return handle_request_cache(req);
}
std::unique_ptr<http::response> file_backend::handle_error(const http::status_codes& error) {
std::string filename = "./error_pages/" + std::to_string(error) + ".html";
auto file_info = file_cache.find(filename);
http::status_codes status = error;
if (file_info == file_cache.end()) {
status = http::status_codes::NOT_FOUND;
filename = "./error_pages/404.html";
file_info = file_cache.find(filename);
}
std::unique_ptr<http::response> resp = std::make_unique<http::response>();
resp->add_body_ref(&file_info->second);
resp->add_status(error);
resp->add_header(http::header("Content-Type", "text/html"), false);
return resp;
}
};

View file

@ -0,0 +1,22 @@
#pragma once
#include "backend.hpp"
namespace anthracite::backends {
class file_backend : public backend {
private:
std::unordered_map<std::string,std::string> file_cache;
std::string file_dir;
std::unique_ptr<http::response> handle_request_cache(http::request& req);
void populate_cache_dir(std::string dir);
void populate_cache();
public:
file_backend(std::string dir = "./www") ;
std::unique_ptr<http::response> handle_request(http::request& req) override;
std::unique_ptr<http::response> handle_error(const http::status_codes& error);
};
};

244
lib/http/constants.hpp Normal file
View file

@ -0,0 +1,244 @@
#pragma once
#include <string>
#include <unordered_map>
namespace anthracite::http {
constexpr int HEADER_BYTES = 8190;
enum method {
GET,
POST,
DELETE,
PUT,
PATCH,
HEAD,
OPTIONS,
CONNECT,
TRACE,
COPY,
LINK,
UNLINK,
PURGE,
LOCK,
UNLOCK,
PROPFIND,
VIEW,
UNKNOWN
};
enum status_codes {
CONTINUE = 100,
SWITCHING_PROTOCOLS = 101,
PROCESSING = 102,
EARLY_HINTS = 103,
OK = 200,
CREATED = 201,
ACCEPTED = 202,
NON_AUTHORITATIVE_INFORMATION = 203,
NO_CONTENT = 204,
RESET_CONTENT = 205,
PARTIAL_CONTENT = 206,
MULTI_STATUS = 207,
ALREADY_REPORTED = 208,
IM_USED = 226,
MULTIPLE_CHOICES = 300,
MOVED_PERMANENTLY = 301,
FOUND = 302,
SEE_OTHER = 303,
NOT_MODIFIED = 304,
USE_PROXY = 305,
TEMPORARY_REDIRECT = 307,
PERMANENT_REDIRECT = 308,
BAD_REQUEST = 400,
UNAUTHORIZED = 401,
PAYMENT_REQUIRED = 402,
FORBIDDEN = 403,
NOT_FOUND = 404,
METHOD_NOT_ALLOWED = 405,
NOT_ACCEPTABLE = 406,
PROXY_AUTHENTICATION_REQUIRED = 407,
REQUEST_TIMEOUT = 408,
CONFLICT = 409,
GONE = 410,
LENGTH_REQUIRED = 411,
PRECONDITION_FAILED = 412,
PAYLOAD_TOO_LARGE = 413,
URI_TOO_LONG = 414,
UNSUPPORTED_MEDIA_TYPE = 415,
RANGE_NOT_SATISFIABLE = 416,
EXPECTATION_FAILED = 417,
I_AM_A_TEAPOT = 418,
ENHANCE_YOUR_CALM = 420,
MISDIRECTED_REQUEST = 421,
UNPROCESSABLE_ENTITY = 422,
LOCKED = 423,
FAILED_DEPENDENCY = 424,
TOO_EARLY = 425,
UPGRADE_REQUIRED = 426,
PRECONDITION_REQUIRED = 428,
TOO_MANY_REQUESTS = 429,
REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
UNAVAILABLE_FOR_LEGAL_REASONS = 451,
INTERNAL_SERVER_ERROR = 500,
NOT_IMPLEMENTED = 501,
BAD_GATEWAY = 502,
SERVICE_UNAVAILABLE = 503,
GATEWAY_TIMEOUT = 504,
HTTP_VERSION_NOT_SUPPORTED = 505,
VARIANT_ALSO_NEGOTIATES = 506,
INSUFFICIENT_STORAGE = 507,
LOOP_DETECTED = 508,
NOT_EXTENDED = 510,
NETWORK_AUTHENTICATION_REQUIRED = 511
};
static std::unordered_map<std::string, method> const method_map = {
{ "GET", method::GET },
{ "POST", method::POST },
{ "DELETE", method::DELETE },
{ "PUT", method::PUT },
{ "PATCH", method::PATCH },
{ "HEAD", method::HEAD },
{ "OPTIONS", method::OPTIONS },
{ "CONNECT", method::CONNECT },
{ "TRACE", method::TRACE },
{ "COPY", method::COPY },
{ "LINK", method::LINK },
{ "UNLINK", method::UNLINK },
{ "PURGE", method::PURGE },
{ "LOCK", method::LOCK },
{ "UNLOCK", method::UNLOCK },
{ "PROPFIND", method::PROPFIND },
{ "VIEW", method::VIEW },
{ "UNKNOWN", method::UNKNOWN }
};
static std::unordered_map<method, std::string> const reverse_method_map = {
{ method::GET, "GET" },
{ method::POST, "POST" },
{ method::DELETE, "DELETE" },
{ method::PUT, "PUT" },
{ method::PATCH, "PATCH" },
{ method::HEAD, "HEAD" },
{ method::OPTIONS, "OPTIONS" },
{ method::CONNECT, "CONNECT" },
{ method::TRACE, "TRACE" },
{ method::COPY, "COPY" },
{ method::LINK, "LINK" },
{ method::UNLINK, "UNLINK" },
{ method::PURGE, "PURGE" },
{ method::LOCK, "LOCK" },
{ method::UNLOCK, "UNLOCK" },
{ method::PROPFIND, "PROPFIND" },
{ method::VIEW, "VIEW" },
{ method::UNKNOWN, "UNKNOWN" }
};
static std::unordered_map<int, std::string> const status_map = {
{ 100, "CONTINUE" },
{ 101, "SWITCHING PROTOCOLS" },
{ 200, "OK" },
{ 201, "CREATED" },
{ 202, "ACCEPTED" },
{ 203, "NON-AUTHORITATIVE INFORMATION" },
{ 204, "NO CONTENT" },
{ 205, "RESET CONTENT" },
{ 206, "PARTIAL CONTENT" },
{ 300, "MULTIPLE CHOICES" },
{ 301, "MOVED PERMANENTLY" },
{ 302, "FOUND" },
{ 303, "SEE OTHER" },
{ 304, "NOT MODIFIED" },
{ 305, "USE PROXY" },
{ 307, "TEMPORARY REDIRECT" },
{ 400, "BAD REQUEST" },
{ 401, "UNAUTHORIZED" },
{ 402, "PAYMENT REQUIRED" },
{ 403, "FORBIDDEN" },
{ 404, "NOT FOUND" },
{ 405, "METHOD NOT ALLOWED" },
{ 406, "NOT ACCEPTABLE" },
{ 407, "PROXY AUTHENTICATION REQUIRED" },
{ 408, "REQUEST TIMEOUT" },
{ 409, "CONFLICT" },
{ 410, "GONE" },
{ 411, "LENGTH REQUIRED" },
{ 412, "PRECONDITION FAILED" },
{ 413, "PAYLOAD TOO LARGE" },
{ 414, "URI TOO LONG" },
{ 415, "UNSUPPORTED MEDIA TYPE" },
{ 416, "RANGE NOT SATISFIABLE" },
{ 417, "EXPECTATION FAILED" },
{ 418, "I'M A TEAPOT" },
{ 421, "MISDIRECTED REQUEST" },
{ 422, "UNPROCESSABLE ENTITY" },
{ 423, "LOCKED" },
{ 424, "FAILED DEPENDENCY" },
{ 426, "UPGRADE REQUIRED" },
{ 428, "PRECONDITION REQUIRED" },
{ 429, "TOO MANY REQUESTS" },
{ 431, "REQUEST HEADER FIELDS TOO LARGE" },
{ 451, "UNAVAILABLE FOR LEGAL REASONS" },
{ 500, "INTERNAL SERVER ERROR" },
{ 501, "NOT IMPLEMENTED" },
{ 502, "BAD GATEWAY" },
{ 503, "SERVICE UNAVAILABLE" },
{ 504, "GATEWAY TIMEOUT" },
{ 505, "HTTP VERSION NOT SUPPORTED" },
{ 506, "VARIANT ALSO NEGOTIATES" },
{ 507, "INSUFFICIENT STORAGE" },
{ 508, "LOOP DETECTED" },
{ 510, "NOT EXTENDED" },
{ 511, "NETWORK AUTHENTICATION REQUIRED" },
{ 420, "ENHANCE YOUR CALM" }
};
static std::unordered_map<std::string, std::string> const mime_types = {
{ "html", "text/html" },
{ "css", "text/css" },
{ "js", "application/javascript" },
{ "json", "application/json" },
{ "pdf", "application/pdf" },
{ "ico", "image/x-icon" },
{ "jpg", "image/jpeg" },
{ "jpeg", "image/jpeg" },
{ "png", "image/png" },
{ "gif", "image/gif" },
{ "bmp", "image/bmp" },
{ "mp4", "video/mp4" },
{ "avi", "video/x-msvideo" },
{ "mkv", "video/x-matroska" },
{ "mov", "video/quicktime" },
{ "wmv", "video/x-ms-wmv" },
};
enum version { HTTP_0_9,
HTTP_1_0,
HTTP_1_1,
HTTP_2_0,
HTTP_3_0 };
static std::unordered_map<std::string, version> const version_map = {
// This is because HTTP 0.9 didn't specify version in the header
{ "", HTTP_0_9 },
{ "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<version, std::string> const 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" }
};
};

60
lib/http/header_query.hpp Normal file
View file

@ -0,0 +1,60 @@
#pragma once
#include <string>
namespace anthracite::http {
class name_value {
private:
std::string _name;
std::string _value;
protected:
name_value() {}
public:
name_value(std::string name, std::string value)
: _name(std::move(name))
, _value(std::move(value))
{
}
virtual ~name_value() = default;
name_value(const name_value&) = default;
name_value& operator=(name_value const&) = default;
name_value(name_value&&) = default;
name_value& operator=(name_value&&) = default;
std::string& name() { return _name; }
std::string& value() { return _value; }
virtual std::string to_string() { return ""; }
};
class header : public name_value {
public:
header()
: name_value()
{
}
header(std::string name, std::string value)
: name_value(name, value)
{
}
std::string to_string() override { return name() + ": " + value() + "\r\n"; }
};
class query_param : public name_value {
public:
query_param()
: name_value()
{
}
query_param(std::string name, std::string value)
: name_value(name, value)
{
}
std::string to_string() override { return name() + "=" + value(); }
};
};

View file

165
lib/http/request.cpp Normal file
View file

@ -0,0 +1,165 @@
#include "request.hpp"
#include "constants.hpp"
#include "../log/log.hpp"
#include <stdio.h>
namespace anthracite::http {
request::request(std::string& raw_data, const std::string& client_ip)
: _path(""), _client_ipaddr(client_ip)
{
parser_state state = METHOD;
std::string scratch = "";
std::string scratch_2 = "";
for (int i = 0; i < raw_data.length(); i++) {
switch (state) {
case METHOD: {
if (raw_data[i] == ' ') {
if (method_map.find(scratch) == method_map.end()) {
_method = method::UNKNOWN;
} else {
_method = 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 = version_map.find(scratch)->second;
scratch = "";
state = HEADER_NAME;
} else if (raw_data[i] != '\r') {
scratch += raw_data[i];
}
} break;
case HEADER_NAME: {
if (raw_data[i] == '\n') {
scratch = "";
scratch_2 = "";
state = BODY_CONTENT;
break;
} else if (raw_data[i] == ' ') {
scratch = "";
break;
} else if (raw_data[i] == ':') {
state = HEADER_VALUE;
i++;
} else {
scratch += raw_data[i];
}
} break;
case HEADER_VALUE: {
if (raw_data[i] == '\n') {
_headers[scratch] = header(scratch, scratch_2);
scratch = "";
scratch_2 = "";
state = HEADER_NAME;
} else if (raw_data[i] != '\r') {
scratch_2 += raw_data[i];
}
} break;
case BODY_CONTENT: {
_body_content += raw_data[i];
} break;
}
}
}
std::string request::path() { return _path; }
method request::get_method() { return _method; }
std::string request::client_ip() { return _client_ipaddr; }
version request::get_http_version() {
return _http_version;
}
bool request::is_supported_version() {
//log::err << reverse_version_map.find(_http_version)->second << std::endl;
return _http_version == HTTP_1_1 || _http_version == HTTP_1_0;
}
bool request::close_connection() {
const auto& header = _headers.find("Connection");
const bool found = header != _headers.end();
if(found && header->second.value() == "keep-alive") {
return false;
}
return true;
}
std::string request::to_string()
{
std::string response = "";
response += reverse_method_map.find(_method)->second + " " + _path + "?";
for (auto qp : _query_params) {
response += qp.second.to_string() + "&";
}
response += " " + 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;
}
};

39
lib/http/request.hpp Normal file
View file

@ -0,0 +1,39 @@
#pragma once
#include <string>
#include <unordered_map>
#include "./header_query.hpp"
#include "./constants.hpp"
namespace anthracite::http {
class request {
private:
enum parser_state { METHOD,
PATH,
QUERY_PARAM_NAME,
QUERY_PARAM_VALUE,
VERSION,
HEADER_NAME,
HEADER_VALUE,
BODY_CONTENT };
method _method;
version _http_version;
std::string _path;
std::string _client_ipaddr;
std::string _body_content;
std::unordered_map<std::string, header> _headers; // kinda goofy, whatever
std::unordered_map<std::string, query_param> _query_params; // kinda goofy, whatever
public:
request(std::string& raw_data, const std::string& client_ip);
std::string path();
method get_method();
std::string client_ip();
version get_http_version();
bool is_supported_version();
bool close_connection();
std::string to_string();
};
};

58
lib/http/response.cpp Normal file
View file

@ -0,0 +1,58 @@
#include "response.hpp"
#include "../version.hpp"
namespace anthracite::http {
response::response() {};
int response::status_code() { return _status_code; }
void response::add_body(const std::string body) {
_content_noref = body;
_content = &_content_noref;
}
void response::add_body_ref(std::string* body) {
_content = body;
}
void response::add_header(header header, bool override_existing)
{
if (override_existing || _headers.find(header.name()) == _headers.end()) {
_headers[header.name()] = header;
}
}
void response::add_status(int code) {
_status_code = code;
}
std::string& response::content()
{
return *_content;
}
std::string response::header_to_string()
{
std::string response = "";
response += "HTTP/1.1 " + std::to_string(_status_code) + " " + status_map.find(_status_code)->second + "\r\n";
add_header(header("Content-Length", std::to_string(_content->length())), false);
add_header(header("Server", ANTHRACITE_FULL_VERSION_STRING), false);
add_header(header("Origin-Server", ANTHRACITE_FULL_VERSION_STRING), false);
for (auto header : _headers) {
response += header.second.to_string();
}
response += "\r\n";
return response;
}
std::string response::to_string()
{
return header_to_string() + *_content;
}
};

31
lib/http/response.hpp Normal file
View file

@ -0,0 +1,31 @@
#pragma once
#include <string>
#include <unordered_map>
#include "header_query.hpp"
#include "constants.hpp"
#include <optional>
namespace anthracite::http {
class response {
private:
int _status_code;
std::string* _content;
std::string _content_noref;
std::unordered_map<std::string, header> _headers; // kinda goofy, whatever
public:
response();
int status_code();
void add_body(const std::string body);
void add_body_ref(std::string* body);
void add_status(int);
void add_header(header header, bool override_existing = true);
std::string& content();
std::string header_to_string();
std::string to_string();
};
};

22
lib/log/log.cpp Normal file
View file

@ -0,0 +1,22 @@
#include "./log.hpp"
namespace anthracite::log {
enum LOG_LEVEL Logger::_level = LOG_LEVEL_NONE;
// TODO: implement logger as a singleton to prevent duplicates
Logger::Logger() = default;
void Logger::initialize(enum LOG_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) {}
int LogBuf::sync() {
if (this->_level <= logger._level) {
std::cout << "[" << this ->_tag << "] " << this->str();
std::cout.flush();
}
this->str("");
return 0;
}
};

53
lib/log/log.hpp Normal file
View file

@ -0,0 +1,53 @@
#pragma once
#include <iostream>
#include <ostream>
#include <sstream>
namespace anthracite::log {
enum LOG_LEVEL {
LOG_LEVEL_NONE = 0,
LOG_LEVEL_ERROR = 1,
LOG_LEVEL_WARN = 2,
LOG_LEVEL_INFO = 3,
LOG_LEVEL_VERBOSE = 4,
LOG_LEVEL_DEBUG = 5
};
class Logger {
friend class LogBuf;
static enum LOG_LEVEL _level;
public:
Logger();
void initialize(enum LOG_LEVEL level);
};
class LogBuf : public std::stringbuf
{
std::string _tag;
std::ostream& _output_stream;
enum LOG_LEVEL _level;
public:
LogBuf(std::ostream& output_stream, const std::string& tag, enum LOG_LEVEL level);
int sync() override;
};
static class Logger logger{};
static class LogBuf errBuf{std::cerr, "EROR", LOG_LEVEL_ERROR};
static std::ostream err(&errBuf);
static class LogBuf warnBuf{std::cerr, "WARN", LOG_LEVEL_WARN};
static std::ostream warn(&warnBuf);
static class LogBuf infoBuf{std::cout, "INFO", LOG_LEVEL_INFO};
static std::ostream info(&infoBuf);
static class LogBuf verboseBuf{std::cout, "VERB", LOG_LEVEL_VERBOSE};
static std::ostream verbose(&verboseBuf);
static class LogBuf debugBuf{std::cout, "DEBG", LOG_LEVEL_DEBUG};
static std::ostream debug(&debugBuf);
};

77
lib/socket/socket.cpp Normal file
View file

@ -0,0 +1,77 @@
#include <arpa/inet.h>
#include <array>
#include <malloc.h>
#include <netinet/in.h>
#include <string>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>
#include <vector>
#include "./socket.hpp"
namespace anthracite::socket {
anthracite_socket::anthracite_socket(int port, int max_queue)
: 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 reuse_opt = 1;
setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &reuse_opt, sizeof(reuse_opt));
bind(server_socket, reinterpret_cast<struct sockaddr*>(&address), sizeof(address));
listen(server_socket, max_queue);
}
void anthracite_socket::wait_for_conn()
{
client_ip = "";
client_socket = accept(server_socket, reinterpret_cast<struct sockaddr*>(&client_addr), &client_addr_len);
std::array<char, INET_ADDRSTRLEN> ip_str { 0 };
inet_ntop(AF_INET, &client_addr.sin_addr, ip_str.data(), INET_ADDRSTRLEN);
client_ip = std::string(ip_str.data());
}
const std::string& anthracite_socket::get_client_ip()
{
return client_ip;
}
void anthracite_socket::close_conn()
{
close(client_socket);
client_socket = -1;
}
void anthracite_socket::send_message(std::string& msg)
{
if (client_socket == -1) {
return;
}
send(client_socket, &msg[0], msg.length(), 0);
}
std::string anthracite_socket::recv_message(int buffer_size)
{
if (client_socket == -1) {
return "";
}
setsockopt(client_socket, SOL_SOCKET, SO_RCVTIMEO, &timeout_tv, sizeof timeout_tv);
std::vector<char> response(buffer_size + 1);
ssize_t result = recv(client_socket, response.data(), buffer_size + 1, 0);
if (result < 1) {
return "";
}
response[buffer_size] = '\0';
return { response.data() };
}
};

34
lib/socket/socket.hpp Normal file
View file

@ -0,0 +1,34 @@
#include <arpa/inet.h>
#include <malloc.h>
#include <netinet/in.h>
#include <string>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>
namespace anthracite::socket {
class anthracite_socket {
static const int MAX_QUEUE_LENGTH = 100;
private:
int server_socket;
int client_socket {};
std::string client_ip;
struct sockaddr_in client_addr {};
socklen_t client_addr_len {};
static constexpr struct timeval timeout_tv {
.tv_sec = 5, .tv_usec = 0
};
public:
anthracite_socket(int port, int max_queue = MAX_QUEUE_LENGTH);
void wait_for_conn();
const std::string& get_client_ip();
void close_conn();
void send_message(std::string& msg);
std::string recv_message(int buffer_size);
};
};

6
lib/version.hpp Normal file
View file

@ -0,0 +1,6 @@
#pragma once
#include <string>
extern const std::string ANTHRACITE_VERSION_STRING;
extern const std::string ANTHRACITE_FULL_VERSION_STRING;